Add data source for VPC Route Entries, imports for VPC, fix update/tests (#6)

This commit is contained in:
Oscar Hermoso
2024-08-25 12:47:14 +08:00
committed by GitHub
parent 1f24f45af7
commit d0716f58af
8 changed files with 287 additions and 41 deletions
+8 -3
View File
@@ -158,7 +158,7 @@ Derive the data source model from the generated `resources.*Model`.
Add a `Configure` method to the data source.
```diff
+ func (d *vpcDataSource) Configure(
+ func (d *exampleDataSource) Configure(
+ _ context.Context,
+ req datasource.ConfigureRequest,
+ resp *datasource.ConfigureResponse,
@@ -182,7 +182,7 @@ Add a `Configure` method to the data source.
Use the generated schema to define the data source schema.
```diff
func (d *vpcDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
func (d *exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
- resp.Schema = schema.Schema{
- Attributes: map[string]schema.Attribute{
- "id": schema.StringAttribute{
@@ -190,7 +190,12 @@ Use the generated schema to define the data source schema.
- },
- },
- }
+ resp.Schema = *convertResourceSchemaToDataSourceSchema(ctx, resources.ExampleResourceSchema(ctx))
+ ds, err := convertResourceSchemaToDataSourceSchema(resources.ExampleResourceSchema(ctx))
+ if err != nil {
+ resp.Diagnostics.AddError("Failed to convert resource schema to data source schema", err.Error())
+ return
+ }
+ resp.Schema = *ds
+ resp.Schema.Description = "TODO"
}
```
+33
View File
@@ -0,0 +1,33 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "binarylane_vpc_route_entries Data Source - terraform-provider-binarylane"
subcategory: ""
description: |-
TODO
---
# binarylane_vpc_route_entries (Data Source)
TODO
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `vpc_id` (Number) The target vpc id.
### Optional
- `route_entries` (List of Object) Submit null to leave unaltered, submit an empty list to clear all route entries. It is not possible to PATCH individual route entries, to alter a route entry submit the entire list of route entries you wish to save. (see [below for nested schema](#nestedatt--route_entries))
<a id="nestedatt--route_entries"></a>
### Nested Schema for `route_entries`
Optional:
- `description` (String)
- `destination` (String)
- `router` (String)
+1
View File
@@ -145,6 +145,7 @@ func (p *binarylaneProvider) DataSources(_ context.Context) []func() datasource.
NewServerDataSource,
NewSshKeyDataSource,
NewVpcDataSource,
NewVpcRouteEntriesDataSource,
}
}
-7
View File
@@ -88,13 +88,6 @@ func (d *vpcDataSource) Read(ctx context.Context, req datasource.ReadRequest, re
data.IpRange = types.StringValue(*vpcResp.JSON200.Vpc.IpRange)
data.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
// routeEntries, routeEntriesDiags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
// resp.Diagnostics.Append(routeEntriesDiags...)
// if resp.Diagnostics.HasError() {
// return
// }
// data.RouteEntries = routeEntries
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
+39 -14
View File
@@ -4,18 +4,21 @@ import (
"context"
"fmt"
"net/http"
"reflect"
"strconv"
"terraform-provider-binarylane/internal/binarylane"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
)
var (
_ resource.Resource = &vpcResource{}
_ resource.ResourceWithConfigure = &vpcResource{}
// _ resource.ResourceWithImportState = &exampleResource{}
_ resource.Resource = &vpcResource{}
_ resource.ResourceWithConfigure = &vpcResource{}
_ resource.ResourceWithImportState = &vpcResource{}
)
func NewVpcResource() resource.Resource {
@@ -121,6 +124,7 @@ func (r *vpcResource) Read(ctx context.Context, req resource.ReadRequest, resp *
fmt.Sprintf("Received %s reading VPC: id=%d. Details: %s", vpcResp.Status(), data.Id.ValueInt64(), vpcResp.Body))
return
}
data.Id = types.Int64Value(*vpcResp.JSON200.Vpc.Id)
data.IpRange = types.StringValue(*vpcResp.JSON200.Vpc.IpRange)
data.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
@@ -129,23 +133,31 @@ func (r *vpcResource) Read(ctx context.Context, req resource.ReadRequest, resp *
}
func (r *vpcResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data vpcResourceModel
var plan, state vpcResourceModel
// Read Terraform plan data into the model
resp.Diagnostics.Append()
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
// Update API call logic
tflog.Debug(ctx, fmt.Sprintf("Updating VPC: id=%d", data.Id.ValueInt64()))
patchRequest := binarylane.PatchVpcRequest{}
vpcResp, err := r.bc.client.PutVpcsVpcIdWithResponse(ctx, data.Id.ValueInt64(), binarylane.UpdateVpcRequest{
Name: data.Name.ValueString(),
})
if !plan.Name.Equal(state.Name) {
patchRequest.Name = plan.Name.ValueStringPointer()
}
if reflect.ValueOf(plan).NumField() == 0 {
return
}
tflog.Debug(ctx, fmt.Sprintf("Updating VPC: id=%d", state.Id.ValueInt64()))
vpcResp, err := r.bc.client.PatchVpcsVpcIdWithResponse(ctx, state.Id.ValueInt64(), patchRequest)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error updating VPC: id=%d", data.Id.ValueInt64()),
fmt.Sprintf("Error updating VPC: id=%d", state.Id.ValueInt64()),
err.Error(),
)
return
@@ -154,15 +166,15 @@ func (r *vpcResource) Update(ctx context.Context, req resource.UpdateRequest, re
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code updating VPC",
fmt.Sprintf("Received %s updating VPC: id=%d. Details: %s", vpcResp.Status(), data.Id.ValueInt64(), vpcResp.Body))
fmt.Sprintf("Received %s updating VPC: id=%d. Details: %s", vpcResp.Status(), state.Id.ValueInt64(), vpcResp.Body))
return
}
data.Id = types.Int64Value(*vpcResp.JSON200.Vpc.Id)
data.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
state.Id = types.Int64Value(*vpcResp.JSON200.Vpc.Id)
state.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}
func (r *vpcResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
@@ -192,3 +204,16 @@ func (r *vpcResource) Delete(ctx context.Context, req resource.DeleteRequest, re
return
}
}
func (r *vpcResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
id, err := strconv.ParseInt(req.ID, 10, 64)
if err != nil {
resp.Diagnostics.AddError(
"Error importing VPC",
fmt.Sprintf("Could not import VPC, unable to parse ID (should be an integer): %s ", err.Error()),
)
return
}
diags := resp.State.SetAttribute(ctx, path.Root("id"), id)
resp.Diagnostics.Append(diags...)
}
+62 -4
View File
@@ -4,6 +4,7 @@ import (
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
)
func TestVpcResource(t *testing.T) {
@@ -34,25 +35,82 @@ resource "binarylane_vpc_route_entries" "test" {
}
]
}
data "binarylane_vpc_route_entries" "test" {
vpc_id = binarylane_vpc_route_entries.test.vpc_id
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
// Verify resource values
// Verify binarylane_vpc resource values
resource.TestCheckResourceAttr("binarylane_vpc.test", "name", "tf-test-vpc"),
resource.TestCheckResourceAttr("binarylane_vpc.test", "ip_range", "10.240.0.0/16"),
resource.TestCheckResourceAttrSet("binarylane_vpc.test", "id"),
// Verify data source values
// Verify binarylane_vpc data source values
resource.TestCheckResourceAttr("data.binarylane_vpc.test", "name", "tf-test-vpc"),
resource.TestCheckResourceAttr("data.binarylane_vpc.test", "ip_range", "10.240.0.0/16"),
resource.TestCheckResourceAttrSet("data.binarylane_vpc.test", "id"),
// Verify resource values
// Verify binarylane_vpc_route_entries resource values
resource.TestCheckResourceAttrSet("binarylane_vpc_route_entries.test", "vpc_id"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.#", "1"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.0.destination", "0.0.0.0/0"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.0.router", "10.240.0.1"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.0.description", "test"),
// Verify binarylane_vpc_route_entries data source values
resource.TestCheckResourceAttrSet("data.binarylane_vpc_route_entries.test", "vpc_id"),
resource.TestCheckResourceAttr("data.binarylane_vpc_route_entries.test", "route_entries.#", "1"),
resource.TestCheckResourceAttr("data.binarylane_vpc_route_entries.test", "route_entries.0.destination", "0.0.0.0/0"),
resource.TestCheckResourceAttr("data.binarylane_vpc_route_entries.test", "route_entries.0.router", "10.240.0.1"),
resource.TestCheckResourceAttr("data.binarylane_vpc_route_entries.test", "route_entries.0.description", "test"),
),
},
// ImportState testing
{
ResourceName: "binarylane_vpc.test",
ImportState: true,
ImportStateVerify: true,
},
{
ResourceName: "binarylane_vpc_route_entries.test",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIdentifierAttribute: "vpc_id",
ImportStateIdFunc: func(s *terraform.State) (string, error) {
resourceState := s.RootModule().Resources["binarylane_vpc_route_entries.test"]
return resourceState.Primary.Attributes["vpc_id"], nil
},
},
// Update and Read testing
{
Config: providerConfig + `
resource "binarylane_vpc" "test" {
name = "tf-test-vpc-renamed"
ip_range = "10.240.0.0/16"
}
resource "binarylane_vpc_route_entries" "test" {
vpc_id = binarylane_vpc.test.id
route_entries = [
{
description = "test-renamed"
destination = "0.0.0.0/0"
router = "10.240.0.2"
}
]
}`,
Check: resource.ComposeAggregateTestCheckFunc(
// Verify binarylane_vpc resource values updated
resource.TestCheckResourceAttr("binarylane_vpc.test", "name", "tf-test-vpc-renamed"), // Updated name
resource.TestCheckResourceAttr("binarylane_vpc.test", "ip_range", "10.240.0.0/16"),
resource.TestCheckResourceAttrSet("binarylane_vpc.test", "id"),
// Verify binarylane_vpc_route_entries resource values updated
resource.TestCheckResourceAttrSet("binarylane_vpc_route_entries.test", "vpc_id"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.#", "1"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.0.destination", "0.0.0.0/0"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.0.router", "10.240.0.2"), // Updated router
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.0.description", "test-renamed"), // Updated description
),
},
// Delete testing automatically occurs in TestCase
},
})
}
@@ -0,0 +1,109 @@
package provider
import (
"context"
"fmt"
"net/http"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
)
var (
_ datasource.DataSource = &vpcRouteEntriesDataSource{}
_ datasource.DataSourceWithConfigure = &vpcRouteEntriesDataSource{}
)
func NewVpcRouteEntriesDataSource() datasource.DataSource {
return &vpcRouteEntriesDataSource{}
}
type vpcRouteEntriesDataSource struct {
bc *BinarylaneClient
}
type vpcRouteEntriesDataSourceModel struct {
resources.VpcRouteEntriesModel
}
func (d *vpcRouteEntriesDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_vpc_route_entries"
}
func (d *vpcRouteEntriesDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
ds, err := convertResourceSchemaToDataSourceSchema(resources.VpcRouteEntriesResourceSchema(ctx))
if err != nil {
resp.Diagnostics.AddError("Failed to convert resource schema to data source schema", err.Error())
return
}
resp.Schema = *ds
resp.Schema.Description = "TODO"
// Overrides
vpcId := resp.Schema.Attributes["vpc_id"]
resp.Schema.Attributes["vpc_id"] = &schema.Int64Attribute{
Description: vpcId.GetDescription(),
MarkdownDescription: vpcId.GetMarkdownDescription(),
Required: true, // VPC ID is required to define the route entries
}
}
func (d *vpcRouteEntriesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data vpcRouteEntriesDataSourceModel
// Read Terraform configuration data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Read API call logic
vpcResp, err := d.bc.client.GetVpcsVpcIdWithResponse(ctx, data.VpcId.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading VPC: vpc_id=%d", data.VpcId.ValueInt64()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code reading VPC",
fmt.Sprintf("Received %s reading VPC: vpc_id=%d. Details: %s", vpcResp.Status(), data.VpcId.ValueInt64(), vpcResp.Body))
return
}
routeEntries, routeEntriesDiags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(routeEntriesDiags...)
if resp.Diagnostics.HasError() {
return
}
data.RouteEntries = routeEntries
// Example data value setting
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (d *vpcRouteEntriesDataSource) Configure(
_ context.Context,
req datasource.ConfigureRequest,
resp *datasource.ConfigureResponse,
) {
if req.ProviderData == nil {
return
}
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData))
return
}
d.bc = &bc
}
+35 -13
View File
@@ -4,21 +4,25 @@ import (
"context"
"fmt"
"net/http"
"strconv"
"terraform-provider-binarylane/internal/binarylane"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)
var (
_ resource.Resource = &vpcRouteEntriesResource{}
_ resource.ResourceWithConfigure = &vpcRouteEntriesResource{}
// _ resource.ResourceWithImportState = &vpcRouteEntriesResource{}
_ resource.Resource = &vpcRouteEntriesResource{}
_ resource.ResourceWithConfigure = &vpcRouteEntriesResource{}
_ resource.ResourceWithImportState = &vpcRouteEntriesResource{}
)
func NewVpcRouteEntriesResource() resource.Resource {
@@ -47,6 +51,9 @@ func (r *vpcRouteEntriesResource) Schema(ctx context.Context, req resource.Schem
Description: vpcId.GetDescription(),
MarkdownDescription: vpcId.GetMarkdownDescription(),
Required: true, // VPC ID is required to define the route entries
Validators: []validator.Int64{
int64validator.AtLeast(1),
},
}
}
@@ -86,7 +93,7 @@ func (r *vpcRouteEntriesResource) Create(ctx context.Context, req resource.Creat
return
}
routeEntriesState, diags := getRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
routeEntriesState, diags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
@@ -111,19 +118,19 @@ func (r *vpcRouteEntriesResource) Read(ctx context.Context, req resource.ReadReq
vpcResp, err := r.bc.client.GetVpcsVpcIdWithResponse(ctx, data.VpcId.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading VPC: vpc_id=%d", data.VpcId.ValueInt64()),
fmt.Sprintf("Error reading VPC route entries: vpc_id=%d", data.VpcId.ValueInt64()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code reading VPC",
fmt.Sprintf("Received %s reading VPC: vpc_id=%d. Details: %s", vpcResp.Status(), data.VpcId.ValueInt64(), vpcResp.Body))
"Unexpected HTTP status code reading VPC route entries",
fmt.Sprintf("Received %s reading VPC route entries: vpc_id=%d. Details: %s", vpcResp.Status(), data.VpcId.ValueInt64(), vpcResp.Body))
return
}
routeEntries, routeEntriesDiags := getRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
routeEntries, routeEntriesDiags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(routeEntriesDiags...)
if resp.Diagnostics.HasError() {
return
@@ -156,7 +163,7 @@ func (r *vpcRouteEntriesResource) Update(ctx context.Context, req resource.Updat
})
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error creating VPC route entries: vpc_id=%d", data.VpcId.ValueInt64()),
fmt.Sprintf("Error updating VPC route entries: vpc_id=%d", data.VpcId.ValueInt64()),
err.Error(),
)
return
@@ -164,12 +171,12 @@ func (r *vpcRouteEntriesResource) Update(ctx context.Context, req resource.Updat
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code creating VPC route entries",
fmt.Sprintf("Received %s creating VPC route entries: vpc_id=%d. Details: %s", vpcResp.Status(), data.VpcId.ValueInt64(), vpcResp.Body))
"Unexpected HTTP status code updating VPC route entries",
fmt.Sprintf("Received %s updating VPC route entries: vpc_id=%d. Details: %s", vpcResp.Status(), data.VpcId.ValueInt64(), vpcResp.Body))
return
}
routeEntriesState, diags := getRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
routeEntriesState, diags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
@@ -226,7 +233,7 @@ func (d *vpcRouteEntriesResource) Configure(_ context.Context, req resource.Conf
d.bc = &bc
}
func getRouteEntriesState(ctx context.Context, routeEntries *[]binarylane.RouteEntry) (basetypes.ListValue, diag.Diagnostics) {
func GetRouteEntriesState(ctx context.Context, routeEntries *[]binarylane.RouteEntry) (basetypes.ListValue, diag.Diagnostics) {
var diags diag.Diagnostics
routeEntriesValue := resources.RouteEntriesValue{}
@@ -253,3 +260,18 @@ func getRouteEntriesState(ctx context.Context, routeEntries *[]binarylane.RouteE
return routeEntriesListValue, diags
}
func (r *vpcRouteEntriesResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
id, err := strconv.ParseInt(req.ID, 10, 64)
if err != nil {
resp.Diagnostics.AddError(
"Error importing VPC route entries",
"Could not import VPC route entries, unexpected error (ID should be an integer): "+err.Error(),
)
return
}
diags := resp.State.SetAttribute(ctx, path.Root("vpc_id"), id)
resp.Diagnostics.Append(diags...)
}