diff --git a/.changelog/0.18.0.toml b/.changelog/0.18.0.toml index 49897478..9625d15c 100644 --- a/.changelog/0.18.0.toml +++ b/.changelog/0.18.0.toml @@ -3,8 +3,13 @@ title = "" description = "" [[features]] -title = "" -description = "" +title = "New resource" +description = "`oxide_address_lot` [#489](https://github.com/oxidecomputer/terraform-provider-oxide/pull/489)." + +[[features]] +title = "New data source" +description = "`oxide_address_lot` [#489](https://github.com/oxidecomputer/terraform-provider-oxide/pull/489)." + [[enhancements]] title = "" diff --git a/Makefile b/Makefile index 2a04513e..05c7cc80 100644 --- a/Makefile +++ b/Makefile @@ -81,6 +81,11 @@ terrafmt: tools @ echo "-> Running terraform docs codeblocks linter" @ find ./docs -type f -name "*.md" -exec $(GOBIN)/terrafmt diff -f {} \; +.PHONY: terrafmt-fmt +terrafmt-fmt: tools + @ echo "-> Running terraform docs codeblocks linter" + @ find ./docs -type f -name "*.md" -exec $(GOBIN)/terrafmt fmt -f {} \; + configfmt: @ echo "-> Running terraform linters on .tf files" @ terraform fmt -write=false -recursive -check diff --git a/docs/data-sources/address_lot.md b/docs/data-sources/address_lot.md new file mode 100644 index 00000000..682ba3be --- /dev/null +++ b/docs/data-sources/address_lot.md @@ -0,0 +1,50 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "oxide_address_lot Data Source - terraform-provider-oxide" +subcategory: "" +description: |- + +--- + +# oxide_address_lot (Data Source) + + + + + + +## Schema + +### Required + +- `name` (String) Name of the address lot. + +### Optional + +- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts)) + +### Read-Only + +- `blocks` (Attributes Set) (see [below for nested schema](#nestedatt--blocks)) +- `description` (String) Description for the address lot. +- `id` (String) Unique, immutable, system-controlled identifier of the address lot. +- `kind` (String) Kind for the address lot. +- `time_created` (String) Timestamp of when this address lot was created. +- `time_modified` (String) Timestamp of when this address lot was last modified. + + +### Nested Schema for `timeouts` + +Optional: + +- `read` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). + + + +### Nested Schema for `blocks` + +Read-Only: + +- `first_address` (String) First address in the lot. +- `id` (String) ID of the address lot block. +- `last_address` (String) Last address in the lot. diff --git a/docs/resources/address_lot.md b/docs/resources/address_lot.md new file mode 100644 index 00000000..9da974a2 --- /dev/null +++ b/docs/resources/address_lot.md @@ -0,0 +1,56 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "oxide_address_lot Resource - terraform-provider-oxide" +subcategory: "" +description: |- + +--- + +# oxide_address_lot (Resource) + + + + + + +## Schema + +### Required + +- `blocks` (Attributes Set) (see [below for nested schema](#nestedatt--blocks)) +- `description` (String) Description for the address lot. +- `kind` (String) Kind for the address lot. Must be one of "infra" or "pool". +- `name` (String) Name of the address lot. + +### Optional + +- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts)) + +### Read-Only + +- `id` (String) Unique, immutable, system-controlled identifier of the address lot. +- `time_created` (String) Timestamp of when this address lot was created. +- `time_modified` (String) Timestamp of when this address lot was last modified. + + +### Nested Schema for `blocks` + +Required: + +- `first_address` (String) First address in the lot. +- `last_address` (String) Last address in the lot. + +Read-Only: + +- `id` (String) ID of the address lot block. + + + +### Nested Schema for `timeouts` + +Optional: + +- `create` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). +- `delete` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Setting a timeout for a Delete operation is only applicable if changes are saved into state before the destroy operation occurs. +- `read` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Read operations occur during any refresh or planning operation when refresh is enabled. +- `update` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). diff --git a/internal/provider/data_source_address_lot.go b/internal/provider/data_source_address_lot.go new file mode 100644 index 00000000..dc360361 --- /dev/null +++ b/internal/provider/data_source_address_lot.go @@ -0,0 +1,164 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/oxidecomputer/oxide.go/oxide" +) + +var _ datasource.DataSource = (*addressLotDataSource)(nil) +var _ datasource.DataSourceWithConfigure = (*addressLotDataSource)(nil) + +type addressLotDataSource struct { + client *oxide.Client +} + +type addressLotDataSourceModel struct { + Blocks []addressLotDataSourceBlockModel `tfsdk:"blocks"` + Description types.String `tfsdk:"description"` + Kind types.String `tfsdk:"kind"` + Name types.String `tfsdk:"name"` + ID types.String `tfsdk:"id"` + TimeCreated types.String `tfsdk:"time_created"` + TimeModified types.String `tfsdk:"time_modified"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +type addressLotDataSourceBlockModel struct { + ID types.String `tfsdk:"id"` + FirstAddress types.String `tfsdk:"first_address"` + LastAddress types.String `tfsdk:"last_address"` +} + +// NewAddressLotDataSource initialises an address_lot datasource. +func NewAddressLotDataSource() datasource.DataSource { + return &addressLotDataSource{} +} + +func (d *addressLotDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = "oxide_address_lot" +} + +// Configure adds the provider configured client to the data source. +func (d *addressLotDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, _ *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + d.client = req.ProviderData.(*oxide.Client) +} + +func (d *addressLotDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + Description: "Name of the address lot.", + }, + "description": schema.StringAttribute{ + Computed: true, + Description: "Description for the address lot.", + }, + "kind": schema.StringAttribute{ + Computed: true, + Description: "Kind for the address lot.", + }, + "blocks": schema.SetNestedAttribute{ + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "ID of the address lot block.", + Computed: true, + }, + "first_address": schema.StringAttribute{ + Description: "First address in the lot.", + Computed: true, + }, + "last_address": schema.StringAttribute{ + Description: "Last address in the lot.", + Computed: true, + }, + }, + }, + }, + "id": schema.StringAttribute{ + Computed: true, + Description: "Unique, immutable, system-controlled identifier of the address lot.", + }, + "time_created": schema.StringAttribute{ + Computed: true, + Description: "Timestamp of when this address lot was created.", + }, + "time_modified": schema.StringAttribute{ + Computed: true, + Description: "Timestamp of when this address lot was last modified.", + }, + "timeouts": timeouts.Attributes(ctx), + }, + } +} + +func (d *addressLotDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var state addressLotDataSourceModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + readTimeout, diags := state.Timeouts.Read(ctx, defaultTimeout()) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + ctx, cancel := context.WithTimeout(ctx, readTimeout) + defer cancel() + + addressLot, err := d.client.NetworkingAddressLotView(ctx, oxide.NetworkingAddressLotViewParams{ + AddressLot: oxide.NameOrId(state.Name.ValueString()), + }) + if err != nil { + resp.Diagnostics.AddError( + "Unable to read address lot:", + "API error: "+err.Error(), + ) + return + } + lot := addressLot.Lot + tflog.Trace(ctx, fmt.Sprintf("read address lot with ID: %v", lot.Id), map[string]any{"success": true}) + + state.ID = types.StringValue(lot.Id) + state.Name = types.StringValue(string(lot.Name)) + state.Kind = types.StringValue(string(lot.Kind)) + state.Description = types.StringValue(lot.Description) + state.TimeCreated = types.StringValue(lot.TimeCreated.String()) + state.TimeModified = types.StringValue(lot.TimeCreated.String()) + + blockModels := make([]addressLotDataSourceBlockModel, len(addressLot.Blocks)) + for index, item := range addressLot.Blocks { + blockModels[index] = addressLotDataSourceBlockModel{ + ID: types.StringValue(item.Id), + FirstAddress: types.StringValue(item.FirstAddress), + LastAddress: types.StringValue(item.LastAddress), + } + } + state.Blocks = blockModels + + // Save state into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } +} diff --git a/internal/provider/data_source_address_lot_test.go b/internal/provider/data_source_address_lot_test.go new file mode 100644 index 00000000..312d25a1 --- /dev/null +++ b/internal/provider/data_source_address_lot_test.go @@ -0,0 +1,57 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package provider + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +var testDataSourceAddressLotConfig = ` +resource "oxide_address_lot" "test" { + description = "a test address lot" + name = "terraform-acc-my-address-lot" + kind = "infra" + blocks = [ + { + first_address = "172.0.1.1" + last_address = "172.0.1.10" + }, + ] +} + +data "oxide_address_lot" "test" { + name = oxide_address_lot.test.name +} +` + +func TestAccDataSourceAddressLot_full(t *testing.T) { + resourceName := "oxide_address_lot.test" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: testDataSourceAddressLotConfig, + Check: checkDataSourceAddressLot(resourceName), + }, + }, + }) +} + +func checkDataSourceAddressLot(dataName string) resource.TestCheckFunc { + return resource.ComposeAggregateTestCheckFunc([]resource.TestCheckFunc{ + resource.TestCheckResourceAttrSet(dataName, "id"), + resource.TestCheckResourceAttr(dataName, "description", "a test address lot"), + resource.TestCheckResourceAttr(dataName, "name", "terraform-acc-my-address-lot"), + resource.TestCheckResourceAttrSet(dataName, "blocks.0.first_address"), + resource.TestCheckResourceAttrSet(dataName, "blocks.0.last_address"), + resource.TestCheckResourceAttr(dataName, "blocks.0.first_address", "172.0.1.1"), + resource.TestCheckResourceAttr(dataName, "blocks.0.last_address", "172.0.1.10"), + resource.TestCheckResourceAttrSet(dataName, "time_created"), + resource.TestCheckResourceAttrSet(dataName, "time_modified"), + }...) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index e48e8b67..275d8a48 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -164,8 +164,10 @@ func (p *oxideProvider) Configure(ctx context.Context, req provider.ConfigureReq // DataSources defines the data sources implemented in the provider. func (p *oxideProvider) DataSources(_ context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ + NewAddressLotDataSource, NewAntiAffinityGroupDataSource, NewDiskDataSource, + NewFloatingIPDataSource, NewImageDataSource, NewImagesDataSource, NewInstanceExternalIPsDataSource, @@ -174,37 +176,37 @@ func (p *oxideProvider) DataSources(_ context.Context) []func() datasource.DataS NewProjectsDataSource, NewSiloDataSource, NewSSHKeyDataSource, + NewSystemIpPoolsDataSource, NewVPCDataSource, NewVPCInternetGatewayDataSource, NewVPCRouterDataSource, NewVPCRouterRouteDataSource, NewVPCSubnetDataSource, - NewFloatingIPDataSource, - NewSystemIpPoolsDataSource, } } // Resources defines the resources implemented in the provider. func (p *oxideProvider) Resources(_ context.Context) []func() resource.Resource { return []func() resource.Resource{ + NewAddressLotResource, NewAntiAffinityGroupResource, NewDiskResource, + NewFloatingIPResource, NewImageResource, NewInstanceResource, NewIPPoolResource, NewIpPoolSiloLinkResource, NewProjectResource, + NewSiloResource, + NewSiloSamlIdentityProviderResource, NewSnapshotResource, NewSSHKeyResource, - NewVPCResource, - NewVPCInternetGatewayResource, NewVPCFirewallRulesResource, + NewVPCInternetGatewayResource, + NewVPCResource, NewVPCRouterResource, NewVPCRouterRouteResource, NewVPCSubnetResource, - NewFloatingIPResource, - NewSiloResource, - NewSiloSamlIdentityProviderResource, } } diff --git a/internal/provider/resource_address_lot.go b/internal/provider/resource_address_lot.go new file mode 100644 index 00000000..ff17c97f --- /dev/null +++ b/internal/provider/resource_address_lot.go @@ -0,0 +1,317 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "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/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + + "github.com/oxidecomputer/oxide.go/oxide" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = (*addressLotResource)(nil) + _ resource.ResourceWithConfigure = (*addressLotResource)(nil) +) + +// NewAddressLotResource is a helper function to simplify the provider implementation. +func NewAddressLotResource() resource.Resource { + return &addressLotResource{} +} + +// addressLotResource is the resource implementation. +type addressLotResource struct { + client *oxide.Client +} + +type addressLotResourceModel struct { + Blocks []addressLotResourceBlockModel `tfsdk:"blocks"` + Description types.String `tfsdk:"description"` + Kind types.String `tfsdk:"kind"` + Name types.String `tfsdk:"name"` + ID types.String `tfsdk:"id"` + TimeCreated types.String `tfsdk:"time_created"` + TimeModified types.String `tfsdk:"time_modified"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +type addressLotResourceBlockModel struct { + ID types.String `tfsdk:"id"` + FirstAddress types.String `tfsdk:"first_address"` + LastAddress types.String `tfsdk:"last_address"` +} + +// Metadata returns the resource type name. +func (r *addressLotResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "oxide_address_lot" +} + +// Configure adds the provider configured client to the data source. +func (r *addressLotResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*oxide.Client) +} + +func (r *addressLotResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +// Schema defines the schema for the resource. +func (r *addressLotResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "Unique, immutable, system-controlled identifier of the address lot.", + }, + "name": schema.StringAttribute{ + Required: true, + Description: "Name of the address lot.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "description": schema.StringAttribute{ + Required: true, + Description: "Description for the address lot.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "kind": schema.StringAttribute{ + Required: true, + Description: `Kind for the address lot. Must be one of "infra" or "pool".`, + Validators: []validator.String{ + stringvalidator.OneOf( + string(oxide.AddressLotKindInfra), + string(oxide.AddressLotKindPool), + ), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "blocks": schema.SetNestedAttribute{ + Required: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "ID of the address lot block.", + Computed: true, + }, + "first_address": schema.StringAttribute{ + Description: "First address in the lot.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "last_address": schema.StringAttribute{ + Description: "Last address in the lot.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + }, + }, + "timeouts": timeouts.Attributes(ctx, timeouts.Opts{ + Create: true, + Read: true, + Update: true, + Delete: true, + }), + "time_created": schema.StringAttribute{ + Computed: true, + Description: "Timestamp of when this address lot was created.", + }, + "time_modified": schema.StringAttribute{ + Computed: true, + Description: "Timestamp of when this address lot was last modified.", + }, + }, + } +} + +// Create creates the resource and sets the initial Terraform state. +func (r *addressLotResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan addressLotResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + createTimeout, diags := plan.Timeouts.Create(ctx, defaultTimeout()) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + ctx, cancel := context.WithTimeout(ctx, createTimeout) + defer cancel() + + blocks := make([]oxide.AddressLotBlockCreate, len(plan.Blocks)) + for i, block := range plan.Blocks { + blocks[i] = oxide.AddressLotBlockCreate{ + FirstAddress: block.FirstAddress.ValueString(), + LastAddress: block.LastAddress.ValueString(), + } + } + params := oxide.NetworkingAddressLotCreateParams{ + Body: &oxide.AddressLotCreate{ + Description: plan.Description.ValueString(), + Name: oxide.Name(plan.Name.ValueString()), + Kind: oxide.AddressLotKind(plan.Kind.ValueString()), + Blocks: blocks, + }, + } + lot, err := r.client.NetworkingAddressLotCreate(ctx, params) + if err != nil { + resp.Diagnostics.AddError( + "Error creating address lot", + "API error: "+err.Error(), + ) + return + } + tflog.Trace(ctx, fmt.Sprintf("created address lot with ID: %v", lot.Lot.Id), map[string]any{"success": true}) + + // Map response body to schema and populate computed attribute values. + plan.ID = types.StringValue(lot.Lot.Id) + plan.TimeCreated = types.StringValue(lot.Lot.TimeCreated.String()) + plan.TimeModified = types.StringValue(lot.Lot.TimeCreated.String()) + + // Populate blocks with computed values. + blockModels := make([]addressLotResourceBlockModel, len(lot.Blocks)) + for index, item := range lot.Blocks { + blockModels[index] = addressLotResourceBlockModel{ + ID: types.StringValue(item.Id), + FirstAddress: types.StringValue(item.FirstAddress), + LastAddress: types.StringValue(item.LastAddress), + } + } + plan.Blocks = blockModels + + // Save plan into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +// Read refreshes the Terraform state with the latest data. +func (r *addressLotResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state addressLotResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + readTimeout, diags := state.Timeouts.Read(ctx, defaultTimeout()) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + ctx, cancel := context.WithTimeout(ctx, readTimeout) + defer cancel() + + addressLot, err := r.client.NetworkingAddressLotView(ctx, oxide.NetworkingAddressLotViewParams{ + AddressLot: oxide.NameOrId(state.ID.ValueString()), + }) + if err != nil { + resp.Diagnostics.AddError( + "Unable to read address lot:", + "API error: "+err.Error(), + ) + return + } + lot := addressLot.Lot + tflog.Trace(ctx, fmt.Sprintf("read address lot with ID: %v", lot.Id), map[string]any{"success": true}) + + state.ID = types.StringValue(lot.Id) + state.Name = types.StringValue(string(lot.Name)) + state.Kind = types.StringValue(string(lot.Kind)) + state.Description = types.StringValue(lot.Description) + state.TimeCreated = types.StringValue(lot.TimeCreated.String()) + state.TimeModified = types.StringValue(lot.TimeCreated.String()) + + blockModels := make([]addressLotResourceBlockModel, len(addressLot.Blocks)) + for index, item := range addressLot.Blocks { + blockModels[index] = addressLotResourceBlockModel{ + ID: types.StringValue(item.Id), + FirstAddress: types.StringValue(item.FirstAddress), + LastAddress: types.StringValue(item.LastAddress), + } + } + state.Blocks = blockModels + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } +} + +// Update updates the resource and sets the updated Terraform state on success. +// Note: the API doesn't currently support updating an Address Lot in place, so we leave this implementation blank and mark all attributes with RequiresReplace. +// TODO: support in-place updates. +func (r *addressLotResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + resp.Diagnostics.AddError( + "Error updating address lot", + "the oxide API currently does not support updating address lots", + ) +} + +// Delete deletes the resource and removes the Terraform state on success. +func (r *addressLotResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state addressLotResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + deleteTimeout, diags := state.Timeouts.Delete(ctx, defaultTimeout()) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + _, cancel := context.WithTimeout(ctx, deleteTimeout) + defer cancel() + + if err := r.client.NetworkingAddressLotDelete( + ctx, + oxide.NetworkingAddressLotDeleteParams{ + AddressLot: oxide.NameOrId(state.ID.ValueString()), + }); err != nil { + if !is404(err) { + resp.Diagnostics.AddError( + "Error deleting Address Lot:", + "API error: "+err.Error(), + ) + return + } + } + tflog.Trace(ctx, fmt.Sprintf("deleted Address Lot with ID: %v", state.ID.ValueString()), map[string]any{"success": true}) +} diff --git a/internal/provider/resource_address_lot_test.go b/internal/provider/resource_address_lot_test.go new file mode 100644 index 00000000..eb04a194 --- /dev/null +++ b/internal/provider/resource_address_lot_test.go @@ -0,0 +1,114 @@ +package provider + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccAddressLot_full(t *testing.T) { + resourceName := "oxide_address_lot.test" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: testResourceAddressLotConfig, + Check: checkResourceAddressLot(resourceName), + }, + { + Config: testResourceAddressLotUpdateConfig, + Check: checkResourceAddressLotUpdate(resourceName), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +var testResourceAddressLotConfig = ` +resource "oxide_address_lot" "test" { + description = "a test address lot" + name = "terraform-acc-my-address-lot" + kind = "infra" + blocks = [ + { + first_address = "172.0.1.1" + last_address = "172.0.1.10" + }, + ] + timeouts = { + read = "1m" + create = "3m" + delete = "2m" + update = "4m" + } +} +` + +var testResourceAddressLotUpdateConfig = ` +resource "oxide_address_lot" "test" { + description = "a test address lot" + name = "terraform-acc-my-address-lot" + kind = "infra" + blocks = [ + { + first_address = "172.0.1.1" + last_address = "172.0.1.10" + }, + { + first_address = "172.0.10.1" + last_address = "172.0.10.10" + }, + ] + timeouts = { + read = "1m" + create = "3m" + delete = "2m" + update = "4m" + } +} +` + +func checkResourceAddressLot(resourceName string) resource.TestCheckFunc { + return resource.ComposeAggregateTestCheckFunc([]resource.TestCheckFunc{ + resource.TestCheckResourceAttrSet(resourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "description", "a test address lot"), + resource.TestCheckResourceAttr(resourceName, "name", "terraform-acc-my-address-lot"), + resource.TestCheckResourceAttrSet(resourceName, "blocks.0.first_address"), + resource.TestCheckResourceAttrSet(resourceName, "blocks.0.last_address"), + resource.TestCheckResourceAttr(resourceName, "blocks.0.first_address", "172.0.1.1"), + resource.TestCheckResourceAttr(resourceName, "blocks.0.last_address", "172.0.1.10"), + resource.TestCheckResourceAttrSet(resourceName, "time_created"), + resource.TestCheckResourceAttrSet(resourceName, "time_modified"), + resource.TestCheckResourceAttr(resourceName, "timeouts.read", "1m"), + resource.TestCheckResourceAttr(resourceName, "timeouts.delete", "2m"), + resource.TestCheckResourceAttr(resourceName, "timeouts.create", "3m"), + resource.TestCheckResourceAttr(resourceName, "timeouts.update", "4m"), + }...) +} + +func checkResourceAddressLotUpdate(resourceName string) resource.TestCheckFunc { + return resource.ComposeAggregateTestCheckFunc([]resource.TestCheckFunc{ + resource.TestCheckResourceAttrSet(resourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "description", "a test address lot"), + resource.TestCheckResourceAttr(resourceName, "name", "terraform-acc-my-address-lot"), + resource.TestCheckResourceAttrSet(resourceName, "blocks.0.first_address"), + resource.TestCheckResourceAttrSet(resourceName, "blocks.0.last_address"), + resource.TestCheckResourceAttrSet(resourceName, "blocks.1.first_address"), + resource.TestCheckResourceAttrSet(resourceName, "blocks.1.last_address"), + resource.TestCheckResourceAttr(resourceName, "blocks.0.first_address", "172.0.1.1"), + resource.TestCheckResourceAttr(resourceName, "blocks.0.last_address", "172.0.1.10"), + resource.TestCheckResourceAttr(resourceName, "blocks.1.first_address", "172.0.10.1"), + resource.TestCheckResourceAttr(resourceName, "blocks.1.last_address", "172.0.10.10"), + resource.TestCheckResourceAttrSet(resourceName, "time_created"), + resource.TestCheckResourceAttrSet(resourceName, "time_modified"), + resource.TestCheckResourceAttr(resourceName, "timeouts.read", "1m"), + resource.TestCheckResourceAttr(resourceName, "timeouts.delete", "2m"), + resource.TestCheckResourceAttr(resourceName, "timeouts.create", "3m"), + resource.TestCheckResourceAttr(resourceName, "timeouts.update", "4m"), + }...) +} diff --git a/internal/provider/resource_ip_pool_test.go b/internal/provider/resource_ip_pool_test.go index 321108e5..c7a1b100 100644 --- a/internal/provider/resource_ip_pool_test.go +++ b/internal/provider/resource_ip_pool_test.go @@ -90,15 +90,15 @@ resource "oxide_ip_pool" "test" { description = "a new description for ip_pool" name = "terraform-acc-myippool-new" ranges = [ - { - first_address = "172.20.15.227" - last_address = "172.20.15.230" - }, - { - first_address = "172.20.15.231" - last_address = "172.20.15.233" - } - ] + { + first_address = "172.20.15.227" + last_address = "172.20.15.230" + }, + { + first_address = "172.20.15.231" + last_address = "172.20.15.233" + } + ] } ` @@ -121,10 +121,10 @@ resource "oxide_ip_pool" "test" { description = "a new description for ip_pool" name = "terraform-acc-myippool-new" ranges = [ - { - first_address = "172.20.15.227" - last_address = "172.20.15.230" - } + { + first_address = "172.20.15.227" + last_address = "172.20.15.230" + } ] } `