diff --git a/docs/data-sources/scaffolding_example.md b/docs/data-sources/example.md similarity index 58% rename from docs/data-sources/scaffolding_example.md rename to docs/data-sources/example.md index 9c9de1e..5aae9ea 100644 --- a/docs/data-sources/scaffolding_example.md +++ b/docs/data-sources/example.md @@ -1,22 +1,16 @@ --- # generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "scaffolding_example Data Source - terraform-provider-scaffolding-framework" +page_title: "virtfusion_example Data Source - terraform-provider-virtfusion" subcategory: "" description: |- Example data source --- -# scaffolding_example (Data Source) +# virtfusion_example (Data Source) Example data source -## Example Usage -```terraform -data "scaffolding_example" "example" { - configurable_attribute = "some-value" -} -``` ## Schema diff --git a/docs/index.md b/docs/index.md index 8eecef4..664d730 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,26 +1,28 @@ --- # generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "scaffolding-framework Provider" +page_title: "virtfusion Provider" subcategory: "" description: |- --- -# scaffolding-framework Provider +# virtfusion Provider ## Example Usage ```terraform -provider "scaffolding" { - # example configuration here +provider "virtfusion" { + endpoint = "example.com" + api_token = "myapikey" } ``` ## Schema -### Optional +### Required -- `endpoint` (String) Example provider attribute +- `api_token` (String) The API token to use for API requests. +- `endpoint` (String) The endpoint to use for API requests. diff --git a/docs/resources/scaffolding_example.md b/docs/resources/scaffolding_example.md deleted file mode 100644 index 47e77ed..0000000 --- a/docs/resources/scaffolding_example.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "scaffolding_example Resource - terraform-provider-scaffolding-framework" -subcategory: "" -description: |- - Example resource ---- - -# scaffolding_example (Resource) - -Example resource - -## Example Usage - -```terraform -resource "scaffolding_example" "example" { - configurable_attribute = "some-value" -} -``` - - -## Schema - -### Optional - -- `configurable_attribute` (String) Example configurable attribute -- `defaulted` (String) Example configurable attribute with default value - -### Read-Only - -- `id` (String) Example identifier diff --git a/docs/resources/server_example.md b/docs/resources/server_example.md new file mode 100644 index 0000000..bbac290 --- /dev/null +++ b/docs/resources/server_example.md @@ -0,0 +1,39 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "virtfusion_server_example Resource - terraform-provider-virtfusion" +subcategory: "" +description: |- + Virtfusion Server Resource +--- + +# virtfusion_server_example (Resource) + +Virtfusion Server Resource + + + + +## Schema + +### Required + +- `hypervisor_id` (Number) Hypervisor ID +- `package_id` (Number) Package ID +- `user_id` (Number) User ID + +### Optional + +- `cores` (Number) How many cores to allocate. Omit to use the default core count from the package. +- `inbound_network_speed` (Number) Inbound network speed in kB/s. Omit to use the default inbound network speed from the package. +- `ipv4` (Number) How many IPv4 addresses to allocate. Default is 1. +- `memory` (Number) How much memory to allocate in MB. Omit to use the default memory size from the package. +- `name` (String) Server name. If omitted, a random UUID will be generated. +- `network_profile` (Number) Network profile ID. Omit to use the default network profile from the package. +- `outbound_network_speed` (Number) Outbound network speed in kB/s. Omit to use the default outbound network speed from the package. +- `storage` (Number) Primary storage size in GB. Omit to use the default storage size from the package. +- `storage_profile` (Number) Storage profile ID. Omit to use the default storage profile from the package. +- `traffic` (Number) How much traffic to allocate in GB. Omit to use the default traffic size from the package. 0=Unlimited + +### Read-Only + +- `id` (Number) Server ID diff --git a/examples/provider/provider.tf b/examples/provider/provider.tf index 942db45..7c7800a 100644 --- a/examples/provider/provider.tf +++ b/examples/provider/provider.tf @@ -1,3 +1,4 @@ -provider "scaffolding" { - # example configuration here +provider "virtfusion" { + endpoint = "example.com" + api_token = "myapikey" } diff --git a/examples/resources/scaffolding_example/resource.tf b/examples/resources/scaffolding_example/resource.tf deleted file mode 100644 index 9ae3f57..0000000 --- a/examples/resources/scaffolding_example/resource.tf +++ /dev/null @@ -1,3 +0,0 @@ -resource "scaffolding_example" "example" { - configurable_attribute = "some-value" -} diff --git a/examples/resources/virtfusion_example/resource.tf b/examples/resources/virtfusion_example/resource.tf new file mode 100644 index 0000000..6eb391d --- /dev/null +++ b/examples/resources/virtfusion_example/resource.tf @@ -0,0 +1,15 @@ +resource "virtfusion_example" "server" { + package_id = 1 + user_id = 1 + hypervisor_id = 1 + ipv4 = 1 + storage = 30 + memory = 1024 + cores = 1 + traffic = 1000 + inbound_network_speed = 100 + outbound_network_speed = 100 + storage_profile = 1 + network_profile = 1 + name = "test" +} diff --git a/go.mod b/go.mod index b9ef545..f1de4f7 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/hashicorp/terraform-provider-scaffolding-framework +module terraform-provider-virtfusion go 1.19 @@ -8,6 +8,7 @@ require ( github.com/hashicorp/terraform-plugin-go v0.18.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-testing v1.4.0 + github.com/hashicorp/terraform-provider-scaffolding-framework v0.0.0-20230818121024-dfa3428de204 ) require ( diff --git a/go.sum b/go.sum index d1313c2..bd677cd 100644 --- a/go.sum +++ b/go.sum @@ -92,6 +92,8 @@ github.com/hashicorp/terraform-plugin-sdk/v2 v2.27.0 h1:I8efBnjuDrgPjNF1MEypHy48 github.com/hashicorp/terraform-plugin-sdk/v2 v2.27.0/go.mod h1:cUEP4ly/nxlHy5HzD6YRrHydtlheGvGRJDhiWqqVik4= github.com/hashicorp/terraform-plugin-testing v1.4.0 h1:DVIXxw7VHZvnwWVik4HzhpC2yytaJ5FpiHxz5debKmE= github.com/hashicorp/terraform-plugin-testing v1.4.0/go.mod h1:b7Bha24iGrbZQjT+ZE8m9crck1YjdVOZ8mfGCQ19OxA= +github.com/hashicorp/terraform-provider-scaffolding-framework v0.0.0-20230818121024-dfa3428de204 h1:+kCE1ctPJ75MRb+r6RS8bpKVe3vzA52ymGEy7ijn1zw= +github.com/hashicorp/terraform-provider-scaffolding-framework v0.0.0-20230818121024-dfa3428de204/go.mod h1:sHl8IBtRBjZLM4bWAA/JN5lTXgvPGITuOKK07YOMdFM= github.com/hashicorp/terraform-registry-address v0.2.1 h1:QuTf6oJ1+WSflJw6WYOHhLgwUiQ0FrROpHPYFtwTYWM= github.com/hashicorp/terraform-registry-address v0.2.1/go.mod h1:BSE9fIFzp0qWsJUUyGquo4ldV9k2n+psif6NYkBRS3Y= github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= diff --git a/internal/provider/example_data_source.go b/internal/provider/example_data_source.go index 585b9d2..9faecbd 100644 --- a/internal/provider/example_data_source.go +++ b/internal/provider/example_data_source.go @@ -17,7 +17,7 @@ import ( // Ensure provider defined types fully satisfy framework interfaces. var _ datasource.DataSource = &ExampleDataSource{} -func NewExampleDataSource() datasource.DataSource { +func NewVirtfusionDataSource() datasource.DataSource { return &ExampleDataSource{} } diff --git a/internal/provider/example_data_source_test.go b/internal/provider/example_data_source_test.go index 6f9aa7d..52933ce 100644 --- a/internal/provider/example_data_source_test.go +++ b/internal/provider/example_data_source_test.go @@ -18,7 +18,7 @@ func TestAccExampleDataSource(t *testing.T) { { Config: testAccExampleDataSourceConfig, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("data.scaffolding_example.test", "id", "example-id"), + resource.TestCheckResourceAttr("data.virtfusion_example.test", "id", "example-id"), ), }, }, @@ -26,7 +26,7 @@ func TestAccExampleDataSource(t *testing.T) { } const testAccExampleDataSourceConfig = ` -data "scaffolding_example" "test" { +data "virtfusion_example" "test" { configurable_attribute = "example" } ` diff --git a/internal/provider/example_resource.go b/internal/provider/example_resource.go deleted file mode 100644 index 70e961a..0000000 --- a/internal/provider/example_resource.go +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package provider - -import ( - "context" - "fmt" - "net/http" - - "github.com/hashicorp/terraform-plugin-framework/path" - "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" -) - -// Ensure provider defined types fully satisfy framework interfaces. -var _ resource.Resource = &ExampleResource{} -var _ resource.ResourceWithImportState = &ExampleResource{} - -func NewExampleResource() resource.Resource { - return &ExampleResource{} -} - -// ExampleResource defines the resource implementation. -type ExampleResource struct { - client *http.Client -} - -// ExampleResourceModel describes the resource data model. -type ExampleResourceModel struct { - ConfigurableAttribute types.String `tfsdk:"configurable_attribute"` - Defaulted types.String `tfsdk:"defaulted"` - Id types.String `tfsdk:"id"` -} - -func (r *ExampleResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_example" -} - -func (r *ExampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - // This description is used by the documentation generator and the language server. - MarkdownDescription: "Example resource", - - Attributes: map[string]schema.Attribute{ - "configurable_attribute": schema.StringAttribute{ - MarkdownDescription: "Example configurable attribute", - Optional: true, - }, - "defaulted": schema.StringAttribute{ - MarkdownDescription: "Example configurable attribute with default value", - Optional: true, - Computed: true, - Default: stringdefault.StaticString("example value when not configured"), - }, - "id": schema.StringAttribute{ - Computed: true, - MarkdownDescription: "Example identifier", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - }, - } -} - -func (r *ExampleResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { - // Prevent panic if the provider has not been configured. - if req.ProviderData == nil { - return - } - - client, ok := req.ProviderData.(*http.Client) - - if !ok { - resp.Diagnostics.AddError( - "Unexpected Resource Configure Type", - fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), - ) - - return - } - - r.client = client -} - -func (r *ExampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var data ExampleResourceModel - - // Read Terraform plan data into the model - resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) - - if resp.Diagnostics.HasError() { - return - } - - // If applicable, this is a great opportunity to initialize any necessary - // provider client data and make a call using it. - // httpResp, err := r.client.Do(httpReq) - // if err != nil { - // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create example, got error: %s", err)) - // return - // } - - // For the purposes of this example code, hardcoding a response value to - // save into the Terraform state. - data.Id = types.StringValue("example-id") - - // Write logs using the tflog package - // Documentation: https://terraform.io/plugin/log - tflog.Trace(ctx, "created a resource") - - // Save data into Terraform state - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) -} - -func (r *ExampleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var data ExampleResourceModel - - // Read Terraform prior state data into the model - resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - - if resp.Diagnostics.HasError() { - return - } - - // If applicable, this is a great opportunity to initialize any necessary - // provider client data and make a call using it. - // httpResp, err := r.client.Do(httpReq) - // if err != nil { - // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read example, got error: %s", err)) - // return - // } - - // Save updated data into Terraform state - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) -} - -func (r *ExampleResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var data ExampleResourceModel - - // Read Terraform plan data into the model - resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) - - if resp.Diagnostics.HasError() { - return - } - - // If applicable, this is a great opportunity to initialize any necessary - // provider client data and make a call using it. - // httpResp, err := r.client.Do(httpReq) - // if err != nil { - // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update example, got error: %s", err)) - // return - // } - - // Save updated data into Terraform state - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) -} - -func (r *ExampleResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var data ExampleResourceModel - - // Read Terraform prior state data into the model - resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - - if resp.Diagnostics.HasError() { - return - } - - // If applicable, this is a great opportunity to initialize any necessary - // provider client data and make a call using it. - // httpResp, err := r.client.Do(httpReq) - // if err != nil { - // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete example, got error: %s", err)) - // return - // } -} - -func (r *ExampleResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) -} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index c326755..725ee23 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -5,13 +5,14 @@ package provider import ( "context" - "net/http" - "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" + "net/http" + "net/url" + "os" ) // Ensure ScaffoldingProvider satisfies various provider interfaces. @@ -28,10 +29,11 @@ type ScaffoldingProvider struct { // ScaffoldingProviderModel describes the provider data model. type ScaffoldingProviderModel struct { Endpoint types.String `tfsdk:"endpoint"` + ApiToken types.String `tfsdk:"api_token"` } func (p *ScaffoldingProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { - resp.TypeName = "scaffolding" + resp.TypeName = "virtfusion" resp.Version = p.version } @@ -39,14 +41,22 @@ func (p *ScaffoldingProvider) Schema(ctx context.Context, req provider.SchemaReq resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ "endpoint": schema.StringAttribute{ - MarkdownDescription: "Example provider attribute", - Optional: true, + MarkdownDescription: "The endpoint to use for API requests.", + Required: true, + }, + "api_token": schema.StringAttribute{ + MarkdownDescription: "The API token to use for API requests.", + Required: true, }, }, } } func (p *ScaffoldingProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + // Check environment variables + apiToken := os.Getenv("VIRTFUSION_API_TOKEN") + endpoint := os.Getenv("VIRTFUSION_ENDPOINT") + var data ScaffoldingProviderModel resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) @@ -58,24 +68,76 @@ func (p *ScaffoldingProvider) Configure(ctx context.Context, req provider.Config // Configuration values are now available. // if data.Endpoint.IsNull() { /* ... */ } + if data.Endpoint.ValueString() != "" { + endpoint = data.Endpoint.ValueString() + } + + if data.ApiToken.ValueString() != "" { + apiToken = data.ApiToken.ValueString() + } + + if apiToken == "" { + resp.Diagnostics.AddError( + "Missing API Token Configuration", + "While configuring the provider, the API token was not found in "+ + "the VIRTFUSION_API_TOKEN environment variable or provider "+ + "configuration block api_token attribute.", + ) + // Not returning early allows the logic to collect all errors. + } + + if endpoint == "" { + resp.Diagnostics.AddError( + "Missing Endpoint Configuration", + "While configuring the provider, the endpoint was not found in "+ + "the VIRTFUSION_ENDPOINT environment variable or provider "+ + "configuration block endpoint attribute.", + ) + // Not returning early allows the logic to collect all errors. + } + + customTransport := &CustomTransport{ + Transport: http.DefaultTransport, + BaseURL: &url.URL{Scheme: "https", Host: endpoint + "/api/v1"}, + Token: apiToken, + } + // Example client configuration for data sources and resources - client := http.DefaultClient + client := &http.Client{ + Transport: customTransport, + } + resp.DataSourceData = client resp.ResourceData = client } func (p *ScaffoldingProvider) Resources(ctx context.Context) []func() resource.Resource { return []func() resource.Resource{ - NewExampleResource, + NewVirtfusionServerResource, } } func (p *ScaffoldingProvider) DataSources(ctx context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ - NewExampleDataSource, + NewVirtfusionDataSource, } } +type CustomTransport struct { + Transport http.RoundTripper + BaseURL *url.URL + Token string +} + +func (c *CustomTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req.Header.Add("Authorization", "Bearer "+c.Token) + req.URL.Scheme = c.BaseURL.Scheme + req.URL.Host = c.BaseURL.Host + req.URL.Path = c.BaseURL.Path + req.URL.Path + + return c.Transport.RoundTrip(req) +} + func New(version string) func() provider.Provider { return func() provider.Provider { return &ScaffoldingProvider{ diff --git a/internal/provider/virtfusion_server_resource.go b/internal/provider/virtfusion_server_resource.go new file mode 100644 index 0000000..0832dd3 --- /dev/null +++ b/internal/provider/virtfusion_server_resource.go @@ -0,0 +1,337 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "io" + "io/ioutil" + "net/http" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &VirtfusionServerResource{} +var _ resource.ResourceWithImportState = &VirtfusionServerResource{} + +func NewVirtfusionServerResource() resource.Resource { + return &VirtfusionServerResource{} +} + +// VirtfusionServerResource defines the resource implementation. +type VirtfusionServerResource struct { + client *http.Client +} + +// VirtfusionServerResourceModel describes the resource data model. +type VirtfusionServerResourceModel struct { + PackageId types.Int64 `tfsdk:"package_id"` + UserId types.Int64 `tfsdk:"user_id"` + HypervisorId types.Int64 `tfsdk:"hypervisor_id"` + Ipv4 types.Int64 `tfsdk:"ipv4"` + Storage types.Int64 `tfsdk:"storage"` + Memory types.Int64 `tfsdk:"memory"` + Cores types.Int64 `tfsdk:"cores"` + Traffic types.Int64 `tfsdk:"traffic"` + InboundNetworkSpeed types.Int64 `tfsdk:"inbound_network_speed"` + OutboundNetworkSpeed types.Int64 `tfsdk:"outbound_network_speed"` + StorageProfile types.Int64 `tfsdk:"storage_profile"` + NetworkProfile types.Int64 `tfsdk:"network_profile"` + Name types.String `tfsdk:"name"` + Id types.Int64 `tfsdk:"id"` +} + +func (r *VirtfusionServerResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_server_example" +} + +func GenerateRandomName() string { + // Generate a random name for the server if one is not provided + // It'll be in a format like "tf-" + return "tf-" + uuid.New().String()[:8] +} + +func (r *VirtfusionServerResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "Virtfusion Server Resource", + + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "Server name. If omitted, a random UUID will be generated.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(GenerateRandomName()), + }, + "package_id": schema.Int64Attribute{ + MarkdownDescription: "Package ID", + Required: true, + }, + "user_id": schema.Int64Attribute{ + MarkdownDescription: "User ID", + Required: true, + }, + "hypervisor_id": schema.Int64Attribute{ + MarkdownDescription: "Hypervisor ID", + Required: true, + }, + "ipv4": schema.Int64Attribute{ + MarkdownDescription: "How many IPv4 addresses to allocate. Default is 1.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(1), + }, + "storage": schema.Int64Attribute{ + MarkdownDescription: "Primary storage size in GB. Omit to use the default storage size from the package.", + Optional: true, + }, + "memory": schema.Int64Attribute{ + MarkdownDescription: "How much memory to allocate in MB. Omit to use the default memory size from the package.", + Optional: true, + }, + "cores": schema.Int64Attribute{ + MarkdownDescription: "How many cores to allocate. Omit to use the default core count from the package.", + Optional: true, + }, + "traffic": schema.Int64Attribute{ + MarkdownDescription: "How much traffic to allocate in GB. Omit to use the default traffic size from the package. 0=Unlimited", + Optional: true, + }, + "inbound_network_speed": schema.Int64Attribute{ + MarkdownDescription: "Inbound network speed in kB/s. Omit to use the default inbound network speed from the package.", + Optional: true, + }, + "outbound_network_speed": schema.Int64Attribute{ + MarkdownDescription: "Outbound network speed in kB/s. Omit to use the default outbound network speed from the package.", + Optional: true, + }, + "storage_profile": schema.Int64Attribute{ + MarkdownDescription: "Storage profile ID. Omit to use the default storage profile from the package.", + Optional: true, + }, + "network_profile": schema.Int64Attribute{ + MarkdownDescription: "Network profile ID. Omit to use the default network profile from the package.", + Optional: true, + }, + "id": schema.Int64Attribute{ + MarkdownDescription: "Server ID", + Computed: true, + }, + }, + } +} + +func (r *VirtfusionServerResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*http.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = client +} + +func (r *VirtfusionServerResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data VirtfusionServerResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + createReq := VirtfusionServerResourceModel{ + PackageId: data.PackageId, + UserId: data.UserId, + HypervisorId: data.HypervisorId, + Ipv4: data.Ipv4, + Storage: data.Storage, + Traffic: data.Traffic, + Memory: data.Memory, + Cores: data.Cores, + InboundNetworkSpeed: data.InboundNetworkSpeed, + OutboundNetworkSpeed: data.OutboundNetworkSpeed, + StorageProfile: data.StorageProfile, + NetworkProfile: data.NetworkProfile, + } + + httpReqBody, err := json.Marshal(createReq) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Create Resource", + "An unexpected error occurred while creating the resource create request. "+ + "Please report this issue to the provider developers.\n\n"+ + "JSON Error: "+err.Error(), + ) + + return + } + + httpReq, err := http.NewRequest("POST", "/servers", bytes.NewBuffer(httpReqBody)) + if err != nil { + resp.Diagnostics.AddError( + "Failed to Create Request", + fmt.Sprintf("Failed to create a new HTTP request: %s", err.Error()), + ) + return + } + + // Add any additional headers (Content-Type, etc.) + httpReq.Header.Set("Content-Type", "application/json") + + httpResponse, err := r.client.Do(httpReq) + if err != nil { + resp.Diagnostics.AddError( + "Failed to Execute Request", + fmt.Sprintf("Failed to execute HTTP request: %s", err.Error()), + ) + return + } + + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + resp.Diagnostics.AddError( + "Failed to Close Request", + fmt.Sprintf("Failed to close HTTP request: %s", err.Error()), + ) + return + } + }(httpResponse.Body) + + if httpResponse.StatusCode != 201 { + resp.Diagnostics.AddError( + "Failed to Create Resource", + fmt.Sprintf("Failed to create resource: %s", httpResponse.Status), + ) + return + } + + responseBody, err := ioutil.ReadAll(httpResponse.Body) + if err != nil { + resp.Diagnostics.AddError( + "Failed to Read Response", + fmt.Sprintf("Failed to read HTTP response body: %s", err.Error()), + ) + return + } + + type ResponseData struct { + Data struct { + Id types.Int64 `json:"id"` + Uuid types.String `json:"uuid"` + } `json:"data"` + } + var responseData ResponseData + + // Unmarshal the JSON response + err = json.Unmarshal(responseBody, &responseData) + if err != nil { + resp.Diagnostics.AddError( + "Failed to Parse Response", + fmt.Sprintf("Failed to parse HTTP response body: %s", err.Error()), + ) + return + } + + // Update the Terraform state with the server ID + data.Id = types.Int64(responseData.Data.Id) + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + + // Write logs using the tflog package + // Documentation: https://terraform.io/plugin/log + //tflog.Trace(ctx, "created a resource") + + // Save data into Terraform state + //resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *VirtfusionServerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data VirtfusionServerResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // If applicable, this is a great opportunity to initialize any necessary + // provider client data and make a call using it. + // httpResp, err := r.client.Do(httpReq) + // if err != nil { + // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read example, got error: %s", err)) + // return + // } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *VirtfusionServerResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data VirtfusionServerResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // If applicable, this is a great opportunity to initialize any necessary + // provider client data and make a call using it. + // httpResp, err := r.client.Do(httpReq) + // if err != nil { + // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update example, got error: %s", err)) + // return + // } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *VirtfusionServerResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data VirtfusionServerResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // If applicable, this is a great opportunity to initialize any necessary + // provider client data and make a call using it. + // httpResp, err := r.client.Do(httpReq) + // if err != nil { + // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete example, got error: %s", err)) + // return + // } +} + +func (r *VirtfusionServerResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/internal/provider/example_resource_test.go b/internal/provider/virtfusion_server_resource_test.go similarity index 73% rename from internal/provider/example_resource_test.go rename to internal/provider/virtfusion_server_resource_test.go index c5464d0..40437c3 100644 --- a/internal/provider/example_resource_test.go +++ b/internal/provider/virtfusion_server_resource_test.go @@ -19,14 +19,14 @@ func TestAccExampleResource(t *testing.T) { { Config: testAccExampleResourceConfig("one"), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("scaffolding_example.test", "configurable_attribute", "one"), - resource.TestCheckResourceAttr("scaffolding_example.test", "defaulted", "example value when not configured"), - resource.TestCheckResourceAttr("scaffolding_example.test", "id", "example-id"), + resource.TestCheckResourceAttr("virtfusion_example.test", "configurable_attribute", "one"), + resource.TestCheckResourceAttr("virtfusion_example.test", "defaulted", "example value when not configured"), + resource.TestCheckResourceAttr("virtfusion_example.test", "id", "example-id"), ), }, // ImportState testing { - ResourceName: "scaffolding_example.test", + ResourceName: "virtfusion_example.test", ImportState: true, ImportStateVerify: true, // This is not normally necessary, but is here because this @@ -39,7 +39,7 @@ func TestAccExampleResource(t *testing.T) { { Config: testAccExampleResourceConfig("two"), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("scaffolding_example.test", "configurable_attribute", "two"), + resource.TestCheckResourceAttr("virtfusion_example.test", "configurable_attribute", "two"), ), }, // Delete testing automatically occurs in TestCase @@ -49,7 +49,7 @@ func TestAccExampleResource(t *testing.T) { func testAccExampleResourceConfig(configurableAttribute string) string { return fmt.Sprintf(` -resource "scaffolding_example" "test" { +resource "virtfusion_example" "test" { configurable_attribute = %[1]q } `, configurableAttribute) diff --git a/main.go b/main.go index e3d16b2..036c216 100644 --- a/main.go +++ b/main.go @@ -9,7 +9,8 @@ import ( "log" "github.com/hashicorp/terraform-plugin-framework/providerserver" - "github.com/hashicorp/terraform-provider-scaffolding-framework/internal/provider" + + "terraform-provider-virtfusion/internal/provider" ) // Run "go generate" to format example terraform files and generate the docs for the registry/website