From ae1cd8bcdbd5693dd3c36f34f17428ac24d15153 Mon Sep 17 00:00:00 2001 From: Amit Shani Date: Sun, 4 Sep 2022 16:01:39 +0300 Subject: [PATCH] fix renaming a relation identifier (#7) * fix renaming a relation identifier * bump ver * docs and lint --- Makefile | 2 +- docs/resources/blueprint.md | 3 +- port/cli/relation.go | 20 +++++- port/resource_port_blueprint.go | 96 ++++++++++++++++++++++++++-- port/resource_port_blueprint_test.go | 86 +++++++++++++++++++++++++ 5 files changed, 198 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 5747b252..87681c6c 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ HOSTNAME=github.com NAMESPACE=port-labs NAME=port-labs BINARY=terraform-provider-${NAME} -VERSION=0.2.0 +VERSION=0.3.1 OS=$(shell go env GOOS) ARCH=$(shell go env GOARCH) OS_ARCH=${OS}_${ARCH} diff --git a/docs/resources/blueprint.md b/docs/resources/blueprint.md index a956b6f0..5f900234 100644 --- a/docs/resources/blueprint.md +++ b/docs/resources/blueprint.md @@ -62,7 +62,8 @@ Required: Optional: - `identifier` (String) The identifier of the relation -- `many` (Boolean) Whether or not the relation is many +- `many` (Boolean) Unsupported ATM. +Whether or not the relation is many - `required` (Boolean) Whether or not the relation is required diff --git a/port/cli/relation.go b/port/cli/relation.go index 82720c05..100171d0 100644 --- a/port/cli/relation.go +++ b/port/cli/relation.go @@ -17,7 +17,7 @@ func (c *PortClient) CreateRelation(ctx context.Context, bpID string, r *Relatio if err != nil { return "", err } - if !result["ok"].(bool) { + if resp.StatusCode() > 299 || resp.StatusCode() < 200 || !result["ok"].(bool) { return "", fmt.Errorf("failed to create relation, got: %s", resp.Body()) } return result["identifier"].(string), nil @@ -53,3 +53,21 @@ func (c *PortClient) ReadRelations(ctx context.Context, blueprintID string) ([]* } return bpRelations, nil } + +func (c *PortClient) DeleteRelation(ctx context.Context, blueprintID, relationID string) error { + url := "v1/blueprints/{blueprint_identifier}/relations/{relation_identifier}" + result := map[string]interface{}{} + resp, err := c.Client.R(). + SetContext(ctx). + SetResult(&result). + SetPathParam("blueprint_identifier", blueprintID). + SetPathParam("relation_identifier", relationID). + Delete(url) + if err != nil { + return err + } + if !result["ok"].(bool) { + return fmt.Errorf("failed to delete relation, got: %s", resp.Body()) + } + return nil +} diff --git a/port/resource_port_blueprint.go b/port/resource_port_blueprint.go index 3e4f1807..b346d2b5 100644 --- a/port/resource_port_blueprint.go +++ b/port/resource_port_blueprint.go @@ -3,6 +3,7 @@ package port import ( "context" + "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -15,7 +16,7 @@ func newBlueprintResource() *schema.Resource { return &schema.Resource{ Description: "Port blueprint", CreateContext: createBlueprint, - UpdateContext: createBlueprint, + UpdateContext: updateBlueprint, ReadContext: readBlueprint, DeleteContext: deleteBlueprint, Schema: map[string]*schema.Schema{ @@ -68,9 +69,15 @@ func newBlueprintResource() *schema.Resource { Description: "Whether or not the relation is required", }, "many": { - Type: schema.TypeBool, - Optional: true, - Description: "Whether or not the relation is many", + Type: schema.TypeBool, + Optional: true, + ValidateDiagFunc: func(i interface{}, path cty.Path) diag.Diagnostics { + if i.(bool) { + return diag.Errorf("Many relations are not supported") + } + return nil + }, + Description: "Unsupported ATM.\nWhether or not the relation is many", }, }, }, @@ -244,8 +251,7 @@ func deleteBlueprint(ctx context.Context, d *schema.ResourceData, m interface{}) return diags } -func createRelations(ctx context.Context, d *schema.ResourceData, m interface{}) error { - c := m.(*cli.PortClient) +func getRelations(d *schema.ResourceData) (rel []*cli.Relation) { relations, ok := d.GetOk("relations") if !ok { return nil @@ -265,6 +271,18 @@ func createRelations(ctx context.Context, d *schema.ResourceData, m interface{}) if req, ok := relation["required"]; ok { r.Required = req.(bool) } + if m, ok := relation["many"]; ok { + r.Many = m.(bool) + } + rel = append(rel, r) + } + return +} + +func createRelations(ctx context.Context, d *schema.ResourceData, m interface{}) error { + c := m.(*cli.PortClient) + rels := getRelations(d) + for _, r := range rels { _, err := c.CreateRelation(ctx, d.Id(), r) if err != nil { return err @@ -273,6 +291,44 @@ func createRelations(ctx context.Context, d *schema.ResourceData, m interface{}) return nil } +func contains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} + +// patchDeleteDeprecatedRelations deletes relations that are no longer present in the resource. +// This is necessary because we bundled relations inside the blueprint resource. +// In the future, the API of blueprints should support getting the relations and then we can delete this patch. +func patchDeleteDeprecatedRelations(ctx context.Context, d *schema.ResourceData, m interface{}) error { + c := m.(*cli.PortClient) + rels := getRelations(d) + ids := make([]string, len(rels)) + for i, r := range rels { + ids[i] = r.Identifier + } + remoteRelations, err := c.ReadRelations(ctx, d.Id()) + if err != nil { + return err + } + toDel := make([]*cli.Relation, 0) + for _, r := range remoteRelations { + if !contains(ids, r.Identifier) { + toDel = append(toDel, r) + } + } + for _, r := range toDel { + err := c.DeleteRelation(ctx, d.Id(), r.Identifier) + if err != nil { + return err + } + } + return nil +} + func createBlueprint(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { var diags diag.Diagnostics c := m.(*cli.PortClient) @@ -297,6 +353,34 @@ func createBlueprint(ctx context.Context, d *schema.ResourceData, m interface{}) return diags } +func updateBlueprint(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + var diags diag.Diagnostics + c := m.(*cli.PortClient) + b, err := blueprintResourceToBody(d) + if err != nil { + return diag.FromErr(err) + } + var bp *cli.Blueprint + if d.Id() != "" { + bp, err = c.UpdateBlueprint(ctx, b, d.Id()) + } else { + bp, err = c.CreateBlueprint(ctx, b) + } + if err != nil { + return diag.FromErr(err) + } + writeBlueprintComputedFieldsToResource(d, bp) + err = patchDeleteDeprecatedRelations(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + err = createRelations(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + return diags +} + func writeBlueprintComputedFieldsToResource(d *schema.ResourceData, b *cli.Blueprint) { d.SetId(b.Identifier) d.Set("created_at", b.CreatedAt.String()) diff --git a/port/resource_port_blueprint_test.go b/port/resource_port_blueprint_test.go index 0e77d20b..0cc9e972 100644 --- a/port/resource_port_blueprint_test.go +++ b/port/resource_port_blueprint_test.go @@ -181,3 +181,89 @@ func TestAccPortBlueprintUpdate(t *testing.T) { }, }) } + +func TestAccPortBlueprintUpdateRelation(t *testing.T) { + envID := genID() + vmID := genID() + var testAccActionConfigCreate = fmt.Sprintf(` + provider "port-labs" {} + resource "port-labs_blueprint" "Environment" { + title = "Environment" + icon = "Environment" + identifier = "%s" + properties { + identifier = "env_name" + type = "string" + title = "Name" + } + } + resource "port-labs_blueprint" "vm" { + title = "Virtual Machine" + icon = "Azure" + identifier = "%s" + properties { + identifier = "image" + type = "string" + title = "Image" + } + relations { + identifier = "vm-to-environment" + title = "Related Environment" + target = port-labs_blueprint.Environment.identifier + } + } +`, envID, vmID) + var testAccActionConfigUpdate = fmt.Sprintf(` + provider "port-labs" {} + resource "port-labs_blueprint" "Environment" { + title = "Environment" + icon = "Environment" + identifier = "%s" + properties { + identifier = "env_name" + type = "string" + title = "Name" + } + } + resource "port-labs_blueprint" "vm" { + title = "Virtual Machine" + icon = "Azure" + identifier = "%s" + properties { + identifier = "image" + type = "string" + title = "Image" + } + relations { + identifier = "environment" + title = "Related Environment" + target = port-labs_blueprint.Environment.identifier + } + } +`, envID, vmID) + resource.Test(t, resource.TestCase{ + Providers: map[string]*schema.Provider{ + "port-labs": Provider(), + }, + Steps: []resource.TestStep{ + { + Config: testAccActionConfigCreate, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("port-labs_blueprint.vm", "relations.#", "1"), + resource.TestCheckResourceAttr("port-labs_blueprint.vm", "relations.0.title", "Related Environment"), + resource.TestCheckResourceAttr("port-labs_blueprint.vm", "relations.0.target", envID), + resource.TestCheckResourceAttr("port-labs_blueprint.vm", "relations.0.identifier", "vm-to-environment"), + ), + }, + { + Config: testAccActionConfigUpdate, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("port-labs_blueprint.vm", "relations.#", "1"), + resource.TestCheckResourceAttr("port-labs_blueprint.vm", "relations.0.title", "Related Environment"), + resource.TestCheckResourceAttr("port-labs_blueprint.vm", "relations.0.target", envID), + resource.TestCheckResourceAttr("port-labs_blueprint.vm", "relations.0.identifier", "environment"), + ), + }, + }, + }) +}