From c740972781b82ee6376f0f7d8c1e3c2d49b7a567 Mon Sep 17 00:00:00 2001 From: Sebastian Nallar Date: Thu, 5 Dec 2024 09:53:34 -0300 Subject: [PATCH 1/2] feat: metadata specification --- Makefile | 2 +- nullplatform/metadata_specification.go | 116 +++++++++++++ nullplatform/null_client.go | 5 + nullplatform/provider.go | 27 +-- nullplatform/provider_test.go | 1 + .../resource_metadata_specification.go | 162 ++++++++++++++++++ 6 files changed, 299 insertions(+), 14 deletions(-) create mode 100644 nullplatform/metadata_specification.go create mode 100644 nullplatform/resource_metadata_specification.go diff --git a/Makefile b/Makefile index b4106f7..9864e8c 100644 --- a/Makefile +++ b/Makefile @@ -29,4 +29,4 @@ testacc: TF_ACC=1 go test $(TEST) -v $(TESTARGS) -timeout 120m update-docs: - tfplugindocs generate -provider-name nullplatform --rendered-provider-name "Null Platform" + tfplugindocs generate -provider-name nullplatform --rendered-provider-name "Null Platform" \ No newline at end of file diff --git a/nullplatform/metadata_specification.go b/nullplatform/metadata_specification.go new file mode 100644 index 0000000..40fd44d --- /dev/null +++ b/nullplatform/metadata_specification.go @@ -0,0 +1,116 @@ +package nullplatform + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" +) + +const ( + METADATA_SPECIFICATION_PATH = "/metadata_specification" +) + +type MetadataSpecification struct { + Id string `json:"id,omitempty"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Nrn string `json:"nrn"` + Entity string `json:"entity"` + Metadata string `json:"metadata"` + Schema map[string]interface{} `json:"schema"` +} + +func (c *NullClient) CreateMetadataSpecification(m *MetadataSpecification) (*MetadataSpecification, error) { + var buf bytes.Buffer + err := json.NewEncoder(&buf).Encode(*m) + if err != nil { + return nil, fmt.Errorf("failed to encode metadata specification: %v", err) + } + + res, err := c.MakeRequest("POST", METADATA_SPECIFICATION_PATH, &buf) + if err != nil { + return nil, fmt.Errorf("failed to make API request: %v", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + bodyBytes, _ := io.ReadAll(res.Body) + return nil, fmt.Errorf("failed to create metadata specification: status code %d, response: %s", res.StatusCode, string(bodyBytes)) + } + + mRes := &MetadataSpecification{} + if err := json.NewDecoder(res.Body).Decode(mRes); err != nil { + return nil, fmt.Errorf("failed to decode API response: %v", err) + } + + return mRes, nil +} + +func (c *NullClient) UpdateMetadataSpecification(id string, m *MetadataSpecification) (*MetadataSpecification, error) { + path := fmt.Sprintf("%s/%s", METADATA_SPECIFICATION_PATH, id) + + var buf bytes.Buffer + err := json.NewEncoder(&buf).Encode(*m) + if err != nil { + return nil, fmt.Errorf("failed to encode metadata specification: %v", err) + } + + res, err := c.MakeRequest("PATCH", path, &buf) + if err != nil { + return nil, fmt.Errorf("failed to make API request: %v", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + bodyBytes, _ := io.ReadAll(res.Body) + return nil, fmt.Errorf("failed to update metadata specification: status code %d, response: %s", res.StatusCode, string(bodyBytes)) + } + + mRes := &MetadataSpecification{} + if err := json.NewDecoder(res.Body).Decode(mRes); err != nil { + return nil, fmt.Errorf("failed to decode API response: %v", err) + } + + return mRes, nil +} + +func (c *NullClient) GetMetadataSpecification(id string) (*MetadataSpecification, error) { + path := fmt.Sprintf("%s/%s", METADATA_SPECIFICATION_PATH, id) + + res, err := c.MakeRequest("GET", path, nil) + if err != nil { + return nil, fmt.Errorf("failed to make API request: %v", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + bodyBytes, _ := io.ReadAll(res.Body) + return nil, fmt.Errorf("failed to get metadata specification: status code %d, response: %s", res.StatusCode, string(bodyBytes)) + } + + m := &MetadataSpecification{} + if err := json.NewDecoder(res.Body).Decode(m); err != nil { + return nil, fmt.Errorf("failed to decode API response: %v", err) + } + + return m, nil +} + +func (c *NullClient) DeleteMetadataSpecification(id string) error { + path := fmt.Sprintf("%s/%s", METADATA_SPECIFICATION_PATH, id) + + res, err := c.MakeRequest("DELETE", path, nil) + if err != nil { + return fmt.Errorf("failed to make API request: %v", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusNoContent { + bodyBytes, _ := io.ReadAll(res.Body) + return fmt.Errorf("failed to delete metadata specification: status code %d, response: %s", res.StatusCode, string(bodyBytes)) + } + + return nil +} diff --git a/nullplatform/null_client.go b/nullplatform/null_client.go index 261524c..1c18841 100644 --- a/nullplatform/null_client.go +++ b/nullplatform/null_client.go @@ -120,6 +120,11 @@ type NullOps interface { GetAccount(accountId string) (*Account, error) PatchAccount(accountId string, account *Account) error DeleteAccount(accountId string) error + + CreateMetadataSpecification(spec *MetadataSpecification) (*MetadataSpecification, error) + GetMetadataSpecification(specId string) (*MetadataSpecification, error) + UpdateMetadataSpecification(specId string, spec *MetadataSpecification) (*MetadataSpecification, error) + DeleteMetadataSpecification(specId string) error } func (c *NullClient) MakeRequest(method, path string, body *bytes.Buffer) (*http.Response, error) { diff --git a/nullplatform/provider.go b/nullplatform/provider.go index ca9cc8b..4d3eafc 100644 --- a/nullplatform/provider.go +++ b/nullplatform/provider.go @@ -39,19 +39,20 @@ func Provider() *schema.Provider { }, }, ResourcesMap: map[string]*schema.Resource{ - "nullplatform_account": resourceAccount(), - "nullplatform_scope": resourceScope(), - "nullplatform_service": resourceService(), - "nullplatform_link": resourceLink(), - "nullplatform_parameter": resourceParameter(), - "nullplatform_parameter_value": resourceParameterValue(), - "nullplatform_approval_action": resourceApprovalAction(), - "nullplatform_approval_policy": resourceApprovalPolicy(), - "nullplatform_notification_channel": resourceNotificationChannel(), - "nullplatform_runtime_configuration": resourceRuntimeConfiguration(), - "nullplatform_provider_config": resourceProviderConfig(), - "nullplatform_dimension": resourceDimension(), - "nullplatform_dimension_value": resourceDimensionValue(), + "nullplatform_account": resourceAccount(), + "nullplatform_scope": resourceScope(), + "nullplatform_service": resourceService(), + "nullplatform_link": resourceLink(), + "nullplatform_parameter": resourceParameter(), + "nullplatform_parameter_value": resourceParameterValue(), + "nullplatform_approval_action": resourceApprovalAction(), + "nullplatform_approval_policy": resourceApprovalPolicy(), + "nullplatform_notification_channel": resourceNotificationChannel(), + "nullplatform_runtime_configuration": resourceRuntimeConfiguration(), + "nullplatform_provider_config": resourceProviderConfig(), + "nullplatform_metadata_specification": resourceMetadataSpecification(), + "nullplatform_dimension": resourceDimension(), + "nullplatform_dimension_value": resourceDimensionValue(), }, DataSourcesMap: map[string]*schema.Resource{ "nullplatform_scope": dataSourceScope(), diff --git a/nullplatform/provider_test.go b/nullplatform/provider_test.go index 2c945e9..4574011 100644 --- a/nullplatform/provider_test.go +++ b/nullplatform/provider_test.go @@ -41,6 +41,7 @@ func TestProvider_HasChildResources(t *testing.T) { "nullplatform_notification_channel", "nullplatform_runtime_configuration", "nullplatform_provider_config", + "nullplatform_metadata_specification", "nullplatform_dimension", "nullplatform_dimension_value", } diff --git a/nullplatform/resource_metadata_specification.go b/nullplatform/resource_metadata_specification.go new file mode 100644 index 0000000..8338d42 --- /dev/null +++ b/nullplatform/resource_metadata_specification.go @@ -0,0 +1,162 @@ +package nullplatform + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceMetadataSpecification() *schema.Resource { + return &schema.Resource{ + Description: "The metadata_specification resource allows you to manage entity metadata specifications", + + Create: MetadataSpecificationCreate, + Read: MetadataSpecificationRead, + Update: MetadataSpecificationUpdate, + Delete: MetadataSpecificationDelete, + + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + d.Set("id", d.Id()) + return []*schema.ResourceData{d}, nil + }, + }, + + Schema: AddNRNSchema(map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "Metadata specification name", + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: "Metadata specification description", + }, + "entity": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Parent entity that holds metadata information", + }, + "metadata": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Entity metadata key", + }, + "schema": { + Type: schema.TypeString, + Required: true, + Description: "JSON schema definition for the metadata specification", + DiffSuppressFunc: suppressEquivalentJSON, + }, + }), + } +} + +func MetadataSpecificationCreate(d *schema.ResourceData, m interface{}) error { + nullOps := m.(NullOps) + + nrn, err := ConstructNRNFromComponents(d, nullOps) + if err != nil { + return fmt.Errorf("error constructing NRN: %v", err) + } + + schemaJSON := d.Get("schema").(string) + var schemaMap map[string]interface{} + if err := json.Unmarshal([]byte(schemaJSON), &schemaMap); err != nil { + return fmt.Errorf("error parsing schema JSON: %v", err) + } + + spec := &MetadataSpecification{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + Nrn: nrn, + Entity: d.Get("entity").(string), + Metadata: d.Get("metadata").(string), + Schema: schemaMap, + } + + created, err := nullOps.CreateMetadataSpecification(spec) + if err != nil { + return err + } + + d.SetId(created.Id) + return MetadataSpecificationRead(d, m) +} + +func MetadataSpecificationRead(d *schema.ResourceData, m interface{}) error { + nullOps := m.(NullOps) + + spec, err := nullOps.GetMetadataSpecification(d.Id()) + if err != nil { + return err + } + + if err := d.Set("name", spec.Name); err != nil { + return err + } + if err := d.Set("description", spec.Description); err != nil { + return err + } + if err := d.Set("nrn", spec.Nrn); err != nil { + return err + } + if err := d.Set("entity", spec.Entity); err != nil { + return err + } + if err := d.Set("metadata", spec.Metadata); err != nil { + return err + } + + schemaJSON, err := json.Marshal(spec.Schema) + if err != nil { + return fmt.Errorf("error serializing schema to JSON: %v", err) + } + if err := d.Set("schema", string(schemaJSON)); err != nil { + return err + } + + return nil +} + +func MetadataSpecificationUpdate(d *schema.ResourceData, m interface{}) error { + nullOps := m.(NullOps) + + spec := &MetadataSpecification{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + } + + if d.HasChange("schema") { + schemaJSON := d.Get("schema").(string) + var schemaMap map[string]interface{} + if err := json.Unmarshal([]byte(schemaJSON), &schemaMap); err != nil { + return fmt.Errorf("error parsing schema JSON: %v", err) + } + spec.Schema = schemaMap + } + + _, err := nullOps.UpdateMetadataSpecification(d.Id(), spec) + if err != nil { + return err + } + + return MetadataSpecificationRead(d, m) +} + +func MetadataSpecificationDelete(d *schema.ResourceData, m interface{}) error { + nullOps := m.(NullOps) + + err := nullOps.DeleteMetadataSpecification(d.Id()) + if err != nil { + return err + } + + d.SetId("") + return nil +} From 998e821fc3c51d1df5f841161511442fe5b78f00 Mon Sep 17 00:00:00 2001 From: Sebastian Nallar Date: Thu, 5 Dec 2024 12:19:02 -0300 Subject: [PATCH 2/2] fix: fix update/create/delete --- nullplatform/metadata_specification.go | 16 +++++++++------- nullplatform/resource_metadata_specification.go | 12 +++++++++--- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/nullplatform/metadata_specification.go b/nullplatform/metadata_specification.go index 40fd44d..4459a6f 100644 --- a/nullplatform/metadata_specification.go +++ b/nullplatform/metadata_specification.go @@ -9,17 +9,17 @@ import ( ) const ( - METADATA_SPECIFICATION_PATH = "/metadata_specification" + METADATA_SPECIFICATION_PATH = "/metadata/metadata_specification" ) type MetadataSpecification struct { Id string `json:"id,omitempty"` - Name string `json:"name"` + Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` - Nrn string `json:"nrn"` - Entity string `json:"entity"` - Metadata string `json:"metadata"` - Schema map[string]interface{} `json:"schema"` + Nrn string `json:"nrn,omitempty"` + Entity string `json:"entity,omitempty"` + Metadata string `json:"metadata,omitempty"` + Schema map[string]interface{} `json:"schema,omitempty"` } func (c *NullClient) CreateMetadataSpecification(m *MetadataSpecification) (*MetadataSpecification, error) { @@ -101,7 +101,9 @@ func (c *NullClient) GetMetadataSpecification(id string) (*MetadataSpecification func (c *NullClient) DeleteMetadataSpecification(id string) error { path := fmt.Sprintf("%s/%s", METADATA_SPECIFICATION_PATH, id) - res, err := c.MakeRequest("DELETE", path, nil) + emptyBody := bytes.NewBuffer([]byte("{}")) + + res, err := c.MakeRequest("DELETE", path, emptyBody) if err != nil { return fmt.Errorf("failed to make API request: %v", err) } diff --git a/nullplatform/resource_metadata_specification.go b/nullplatform/resource_metadata_specification.go index 8338d42..0d5f7c3 100644 --- a/nullplatform/resource_metadata_specification.go +++ b/nullplatform/resource_metadata_specification.go @@ -60,9 +60,15 @@ func resourceMetadataSpecification() *schema.Resource { func MetadataSpecificationCreate(d *schema.ResourceData, m interface{}) error { nullOps := m.(NullOps) - nrn, err := ConstructNRNFromComponents(d, nullOps) - if err != nil { - return fmt.Errorf("error constructing NRN: %v", err) + var nrn string + var err error + if v, ok := d.GetOk("nrn"); ok { + nrn = v.(string) + } else { + nrn, err = ConstructNRNFromComponents(d, nullOps) + if err != nil { + return fmt.Errorf("error constructing NRN: %v %s", err, nrn) + } } schemaJSON := d.Get("schema").(string)