From 26444db2796b8aa8ce96e9e20cd9de060d5cfa73 Mon Sep 17 00:00:00 2001 From: Corentin PAPE Date: Thu, 14 Nov 2024 12:18:46 +0100 Subject: [PATCH 1/7] route service binding resource: migrate to ccv3 --- .../resource_cf_route_service_binding.go | 133 ++++++++++++++---- 1 file changed, 103 insertions(+), 30 deletions(-) diff --git a/cloudfoundry/resource_cf_route_service_binding.go b/cloudfoundry/resource_cf_route_service_binding.go index 6487a2d3b..a5b7f16e1 100644 --- a/cloudfoundry/resource_cf_route_service_binding.go +++ b/cloudfoundry/resource_cf_route_service_binding.go @@ -2,7 +2,14 @@ package cloudfoundry import ( "context" + "fmt" + "log" + "strings" + + "github.com/cloudfoundry/go-cfclient/v3/client" + "github.com/cloudfoundry/go-cfclient/v3/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/terraform-providers/terraform-provider-cloudfoundry/cloudfoundry/managers" "encoding/json" @@ -38,18 +45,16 @@ func resourceRouteServiceBinding() *schema.Resource { ForceNew: true, }, }, + SchemaVersion: 1, + MigrateState: resourceRouteServiceBindingMigrateState, } } -func resourceRouteServiceBindingImport(ctx context.Context, d *schema.ResourceData, meta interface{}) (res []*schema.ResourceData, err error) { - id := d.Id() - if _, _, err = parseID(id); err != nil { - return - } +func resourceRouteServiceBindingImport(ctx context.Context, d *schema.ResourceData, meta any) (res []*schema.ResourceData, err error) { return ImportReadContext(resourceRouteServiceBindingRead)(ctx, d, meta) } -func resourceRouteServiceBindingCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceRouteServiceBindingCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { session := meta.(*managers.Session) var data map[string]interface{} @@ -63,48 +68,116 @@ func resourceRouteServiceBindingCreate(ctx context.Context, d *schema.ResourceDa return diag.FromErr(err) } } - _, err := session.ClientV2.CreateServiceBindingRoute(serviceID, routeID, data) + + jobGUID, _, err := session.ClientGo.ServiceRouteBindings.Create(context.Background(), &resource.ServiceRouteBindingCreate{ + Relationships: resource.ServiceRouteBindingRelationships{ + // ServiceInstance ToOneRelationship `json:"service_instance"` + // // The route that the service instance is bound to + // Route ToOneRelationship `json:"route"` + ServiceInstance: resource.ToOneRelationship{ + Data: &resource.Relationship{ + GUID: serviceID, + }, + }, + Route: resource.ToOneRelationship{ + Data: &resource.Relationship{ + GUID: routeID, + }, + }, + }, + }) + + if err != nil { + return diag.FromErr(err) + } + + if jobGUID != "" { + err = session.ClientGo.Jobs.PollComplete(context.Background(), jobGUID, nil) + } if err != nil { return diag.FromErr(err) } - d.SetId(computeID(serviceID, routeID)) + options := client.NewServiceRouteBindingListOptions() + options.ServiceInstanceGUIDs = client.Filter{Values: []string{serviceID}} + options.RouteGUIDs = client.Filter{Values: []string{routeID}} + + routeBinding, err := session.ClientGo.ServiceRouteBindings.Single(context.Background(), options) + + if err != nil { + return diag.FromErr(err) + } + + d.SetId(routeBinding.GUID) return nil } -func resourceRouteServiceBindingRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceRouteServiceBindingRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { session := meta.(*managers.Session) - serviceID, routeID, err := parseID(d.Id()) + routeServiceBinding, err := session.ClientGo.ServiceRouteBindings.Get(context.Background(), d.Id()) + if err != nil { - return diag.FromErr(err) + if strings.Contains(err.Error(), "CF-ResourceNotFound") { + d.SetId("") + return nil + } + return diag.Errorf("Error when reading routeServiceBinding with id '%s': %s", d.Id(), err) } - routes, _, err := session.ClientV2.GetServiceBindingRoutes(serviceID) + + d.Set("service_instance", routeServiceBinding.Relationships.ServiceInstance.Data.GUID) + d.Set("route", routeServiceBinding.Relationships.Route.Data.GUID) + + return nil +} + +func resourceRouteServiceBindingDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + session := meta.(*managers.Session) + + jobGUID, err := session.ClientGo.ServiceRouteBindings.Delete(context.Background(), d.Id()) + if err != nil { return diag.FromErr(err) } - found := false - for _, route := range routes { - if route.GUID == routeID { - found = true - break - } - } - if !found { - d.SetId("") - return diag.Errorf("Route '%s' not found in service instance '%s'", routeID, serviceID) + if jobGUID != "" { + err = session.ClientGo.Jobs.PollComplete(context.Background(), jobGUID, nil) } + return diag.FromErr(err) +} - d.Set("service_instance", serviceID) - d.Set("route", routeID) - return nil +func resourceRouteServiceBindingMigrateState(v int, inst *terraform.InstanceState, meta any) (*terraform.InstanceState, error) { + switch v { + case 0: + log.Println("[INFO] Found Route Service Binding State v0; migrating to v1: change ID from routeID:serviceID to routeServiceBindingID.") + return migrateRouteServiceBindingStateV0toV1ChangeID(inst, meta) + default: + return inst, fmt.Errorf("Unexpected schema version: %d", v) + } } -func resourceRouteServiceBindingDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func migrateRouteServiceBindingStateV0toV1ChangeID(inst *terraform.InstanceState, meta any) (*terraform.InstanceState, error) { session := meta.(*managers.Session) - serviceID := d.Get("service_instance").(string) - routeID := d.Get("route").(string) - _, err := session.ClientV2.DeleteServiceBindingRoute(serviceID, routeID) - return diag.FromErr(err) + if inst.Empty() { + log.Println("[DEBUG] Empty RouteServiceBinding; nothing to migrate.") + return inst, nil + } + + log.Printf("[DEBUG] Attributes before migration: %#v", inst.Attributes) + options := client.NewServiceRouteBindingListOptions() + options.ServiceInstanceGUIDs = client.Filter{Values: []string{inst.Attributes["service_instance"]}} + options.RouteGUIDs = client.Filter{Values: []string{inst.Attributes["route"]}} + + routeBinding, err := session.ClientGo.ServiceRouteBindings.Single(context.Background(), options) + + if err != nil { + log.Println("[DEBUG] Failed to migrate RouteServiceBinding id: did not find the route service binding.") + return inst, err + } + + inst.Attributes["id"] = routeBinding.GUID + + log.Printf("[DEBUG] Attributes after migration: %#v", inst.Attributes) + + return inst, nil } From da652a5b3535ad8ec9fa0c586ceab9ec322c0e0d Mon Sep 17 00:00:00 2001 From: Corentin PAPE Date: Thu, 14 Nov 2024 12:36:50 +0100 Subject: [PATCH 2/7] use state upgrader instead of migrate --- .../resource_cf_route_service_binding.go | 62 ++++++++++++------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/cloudfoundry/resource_cf_route_service_binding.go b/cloudfoundry/resource_cf_route_service_binding.go index a5b7f16e1..99bbf13eb 100644 --- a/cloudfoundry/resource_cf_route_service_binding.go +++ b/cloudfoundry/resource_cf_route_service_binding.go @@ -2,14 +2,12 @@ package cloudfoundry import ( "context" - "fmt" "log" "strings" "github.com/cloudfoundry/go-cfclient/v3/client" "github.com/cloudfoundry/go-cfclient/v3/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/terraform-providers/terraform-provider-cloudfoundry/cloudfoundry/managers" "encoding/json" @@ -46,7 +44,35 @@ func resourceRouteServiceBinding() *schema.Resource { }, }, SchemaVersion: 1, - MigrateState: resourceRouteServiceBindingMigrateState, + StateUpgraders: []schema.StateUpgrader{ + { + Type: resourceRouteServiceBindingResourceV0().CoreConfigSchema().ImpliedType(), + Upgrade: upgradeStateRouteServiceBindingStateV0toV1ChangeID, + Version: 0, + }, + }, + } +} + +func resourceRouteServiceBindingResourceV0() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "service_instance": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "route": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "json_params": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, } } @@ -145,39 +171,29 @@ func resourceRouteServiceBindingDelete(ctx context.Context, d *schema.ResourceDa return diag.FromErr(err) } -func resourceRouteServiceBindingMigrateState(v int, inst *terraform.InstanceState, meta any) (*terraform.InstanceState, error) { - switch v { - case 0: - log.Println("[INFO] Found Route Service Binding State v0; migrating to v1: change ID from routeID:serviceID to routeServiceBindingID.") - return migrateRouteServiceBindingStateV0toV1ChangeID(inst, meta) - default: - return inst, fmt.Errorf("Unexpected schema version: %d", v) - } -} - -func migrateRouteServiceBindingStateV0toV1ChangeID(inst *terraform.InstanceState, meta any) (*terraform.InstanceState, error) { +func upgradeStateRouteServiceBindingStateV0toV1ChangeID(ctx context.Context, rawState map[string]any, meta any) (map[string]any, error) { session := meta.(*managers.Session) - if inst.Empty() { + if len(rawState) == 0 { log.Println("[DEBUG] Empty RouteServiceBinding; nothing to migrate.") - return inst, nil + return rawState, nil } - log.Printf("[DEBUG] Attributes before migration: %#v", inst.Attributes) + log.Printf("[DEBUG] Attributes before migration: %#v", rawState) options := client.NewServiceRouteBindingListOptions() - options.ServiceInstanceGUIDs = client.Filter{Values: []string{inst.Attributes["service_instance"]}} - options.RouteGUIDs = client.Filter{Values: []string{inst.Attributes["route"]}} + options.ServiceInstanceGUIDs = client.Filter{Values: []string{rawState["service_instance"].(string)}} + options.RouteGUIDs = client.Filter{Values: []string{rawState["route"].(string)}} routeBinding, err := session.ClientGo.ServiceRouteBindings.Single(context.Background(), options) if err != nil { log.Println("[DEBUG] Failed to migrate RouteServiceBinding id: did not find the route service binding.") - return inst, err + return rawState, err } - inst.Attributes["id"] = routeBinding.GUID + rawState["id"] = routeBinding.GUID - log.Printf("[DEBUG] Attributes after migration: %#v", inst.Attributes) + log.Printf("[DEBUG] Attributes after migration: %#v", rawState) - return inst, nil + return rawState, nil } From 4f3e37bc48e5c282357b7e29389efe866f8ef7b7 Mon Sep 17 00:00:00 2001 From: Corentin PAPE Date: Tue, 14 Jan 2025 16:04:17 +0100 Subject: [PATCH 3/7] remove route from state if not found during migration --- cloudfoundry/resource_cf_route_service_binding.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cloudfoundry/resource_cf_route_service_binding.go b/cloudfoundry/resource_cf_route_service_binding.go index 99bbf13eb..4d5d2d93b 100644 --- a/cloudfoundry/resource_cf_route_service_binding.go +++ b/cloudfoundry/resource_cf_route_service_binding.go @@ -187,6 +187,10 @@ func upgradeStateRouteServiceBindingStateV0toV1ChangeID(ctx context.Context, raw routeBinding, err := session.ClientGo.ServiceRouteBindings.Single(context.Background(), options) if err != nil { + if strings.Contains(err.Error(), "CF-ResourceNotFound") { + rawState["id"] = "" + return rawState, nil + } log.Println("[DEBUG] Failed to migrate RouteServiceBinding id: did not find the route service binding.") return rawState, err } From 3407004dd446117f2f9303803e3bed3e15044d74 Mon Sep 17 00:00:00 2001 From: Corentin PAPE Date: Tue, 14 Jan 2025 16:18:00 +0100 Subject: [PATCH 4/7] use proper error message filter --- cloudfoundry/resource_cf_route_service_binding.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudfoundry/resource_cf_route_service_binding.go b/cloudfoundry/resource_cf_route_service_binding.go index 4d5d2d93b..73227f703 100644 --- a/cloudfoundry/resource_cf_route_service_binding.go +++ b/cloudfoundry/resource_cf_route_service_binding.go @@ -187,7 +187,7 @@ func upgradeStateRouteServiceBindingStateV0toV1ChangeID(ctx context.Context, raw routeBinding, err := session.ClientGo.ServiceRouteBindings.Single(context.Background(), options) if err != nil { - if strings.Contains(err.Error(), "CF-ResourceNotFound") { + if strings.Contains(err.Error(), "expected exactly 1 result, but got less or more than 1") { rawState["id"] = "" return rawState, nil } From fe81fdcf29f391d8c6268a0359ef3fa43db4a262 Mon Sep 17 00:00:00 2001 From: Cocossoul <58777660+Cocossoul@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:00:34 +0100 Subject: [PATCH 5/7] match exact error Co-authored-by: Sam Leung --- cloudfoundry/resource_cf_route_service_binding.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudfoundry/resource_cf_route_service_binding.go b/cloudfoundry/resource_cf_route_service_binding.go index 73227f703..e90f2f4ba 100644 --- a/cloudfoundry/resource_cf_route_service_binding.go +++ b/cloudfoundry/resource_cf_route_service_binding.go @@ -187,7 +187,7 @@ func upgradeStateRouteServiceBindingStateV0toV1ChangeID(ctx context.Context, raw routeBinding, err := session.ClientGo.ServiceRouteBindings.Single(context.Background(), options) if err != nil { - if strings.Contains(err.Error(), "expected exactly 1 result, but got less or more than 1") { + if err == client.ErrExactlyOneResultNotReturned { rawState["id"] = "" return rawState, nil } From b5ebe2cec1cd2bed1db660a564f14c32958992df Mon Sep 17 00:00:00 2001 From: Cocossoul <58777660+Cocossoul@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:05:57 +0100 Subject: [PATCH 6/7] better error message in state upgrade --- cloudfoundry/resource_cf_route_service_binding.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudfoundry/resource_cf_route_service_binding.go b/cloudfoundry/resource_cf_route_service_binding.go index e90f2f4ba..0bf52eb6f 100644 --- a/cloudfoundry/resource_cf_route_service_binding.go +++ b/cloudfoundry/resource_cf_route_service_binding.go @@ -191,7 +191,7 @@ func upgradeStateRouteServiceBindingStateV0toV1ChangeID(ctx context.Context, raw rawState["id"] = "" return rawState, nil } - log.Println("[DEBUG] Failed to migrate RouteServiceBinding id: did not find the route service binding.") + log.Println("[DEBUG] Failed to migrate RouteServiceBinding id: error while searching for the route service binding.") return rawState, err } From b5649cbaea38f6a215ac3408af53edae7ef47c79 Mon Sep 17 00:00:00 2001 From: Corentin PAPE Date: Wed, 15 Jan 2025 11:12:58 +0100 Subject: [PATCH 7/7] refacto schema --- .../resource_cf_route_service_binding.go | 28 ++++--------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/cloudfoundry/resource_cf_route_service_binding.go b/cloudfoundry/resource_cf_route_service_binding.go index 0bf52eb6f..027142470 100644 --- a/cloudfoundry/resource_cf_route_service_binding.go +++ b/cloudfoundry/resource_cf_route_service_binding.go @@ -26,27 +26,11 @@ func resourceRouteServiceBinding() *schema.Resource { StateContext: resourceRouteServiceBindingImport, }, - Schema: map[string]*schema.Schema{ - "service_instance": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "route": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "json_params": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - }, + Schema: resourceRouteServiceBindingSchema().Schema, SchemaVersion: 1, StateUpgraders: []schema.StateUpgrader{ { - Type: resourceRouteServiceBindingResourceV0().CoreConfigSchema().ImpliedType(), + Type: resourceRouteServiceBindingSchema().CoreConfigSchema().ImpliedType(), Upgrade: upgradeStateRouteServiceBindingStateV0toV1ChangeID, Version: 0, }, @@ -54,20 +38,20 @@ func resourceRouteServiceBinding() *schema.Resource { } } -func resourceRouteServiceBindingResourceV0() *schema.Resource { +func resourceRouteServiceBindingSchema() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ - "service_instance": &schema.Schema{ + "service_instance": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "route": &schema.Schema{ + "route": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "json_params": &schema.Schema{ + "json_params": { Type: schema.TypeString, Optional: true, ForceNew: true,