From 2b770feca1e34f802b9d8d45987b3c44e09df6ee Mon Sep 17 00:00:00 2001 From: Joseph Sirianni Date: Mon, 10 Feb 2025 11:51:41 -0500 Subject: [PATCH] feat: Generate component IDs before applying new resources (#142) * Generate component ID * Test component func * test id change * add license * lint: package comment --- go.mod | 2 +- internal/component/component.go | 30 +++++++++++++++++++++++++++ internal/component/component_test.go | 30 +++++++++++++++++++++++++++ internal/resource/resource.go | 3 ++- internal/resource/resource_test.go | 9 +++++++- provider/resource_destination.go | 9 +++++++- provider/resource_extension.go | 9 +++++++- provider/resource_generic.go | 14 ++++--------- provider/resource_processor.go | 9 +++++++- provider/resource_processor_bundle.go | 9 +++++++- provider/resource_source.go | 9 +++++++- 11 files changed, 115 insertions(+), 18 deletions(-) create mode 100644 internal/component/component.go create mode 100644 internal/component/component_test.go diff --git a/go.mod b/go.mod index 687bdf6..8134162 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.29.0 github.com/observiq/bindplane-op-enterprise v1.86.0 + github.com/oklog/ulid/v2 v2.1.0 github.com/stretchr/testify v1.10.0 github.com/testcontainers/testcontainers-go v0.35.0 go.uber.org/zap v1.27.0 @@ -135,7 +136,6 @@ require ( github.com/nats-io/nuid v1.0.1 // indirect github.com/observiq/stanza v1.6.1 // indirect github.com/oklog/run v1.0.0 // indirect - github.com/oklog/ulid/v2 v2.1.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden v0.118.0 // indirect github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.118.0 // indirect diff --git a/internal/component/component.go b/internal/component/component.go new file mode 100644 index 0000000..82caeb5 --- /dev/null +++ b/internal/component/component.go @@ -0,0 +1,30 @@ +// Copyright observIQ, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package component provides functions for defining bindplane +// component values. +package component + +import ( + "fmt" + + "github.com/observiq/bindplane-op-enterprise/model" +) + +// NewResourceID wraps model.NewResourceID and returns +// a new resource ID with the `tf` prefix to indicate +// that it was created by the Terraform provider. +func NewResourceID() string { + return fmt.Sprintf("tf-%s", model.NewResourceID()) +} diff --git a/internal/component/component_test.go b/internal/component/component_test.go new file mode 100644 index 0000000..bcf9379 --- /dev/null +++ b/internal/component/component_test.go @@ -0,0 +1,30 @@ +// Copyright observIQ, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package component + +import ( + "strings" + "testing" + + "github.com/oklog/ulid/v2" + "github.com/stretchr/testify/require" +) + +func TestNewResourceID(t *testing.T) { + id := NewResourceID() + require.True(t, strings.HasPrefix(id, "tf-")) + _, err := ulid.Parse(strings.TrimPrefix(id, "tf-")) + require.NoError(t, err) +} diff --git a/internal/resource/resource.go b/internal/resource/resource.go index a71877c..5c1bfa2 100644 --- a/internal/resource/resource.go +++ b/internal/resource/resource.go @@ -28,7 +28,7 @@ import ( // configurations, use AnyResourceFromConfigurationV1. // // rParameters and rProcessors can be nil. -func AnyResourceV1(rName, rType string, rKind model.Kind, rParameters []model.Parameter, rProcessors []model.ResourceConfiguration) (model.AnyResource, error) { +func AnyResourceV1(id, rName, rType string, rKind model.Kind, rParameters []model.Parameter, rProcessors []model.ResourceConfiguration) (model.AnyResource, error) { procs := []map[string]string{} for _, p := range rProcessors { proc := map[string]string{} @@ -47,6 +47,7 @@ func AnyResourceV1(rName, rType string, rKind model.Kind, rParameters []model.Pa APIVersion: "bindplane.observiq.com/v1", Kind: rKind, Metadata: model.Metadata{ + ID: id, Name: rName, }, }, diff --git a/internal/resource/resource_test.go b/internal/resource/resource_test.go index 8fc5f4a..e454fb6 100644 --- a/internal/resource/resource_test.go +++ b/internal/resource/resource_test.go @@ -24,6 +24,7 @@ import ( func TestAnyResourceV1(t *testing.T) { cases := []struct { name string + id string rName string rType string rkind model.Kind @@ -33,6 +34,7 @@ func TestAnyResourceV1(t *testing.T) { }{ { "source", + "tf-source", "my-host", "host", model.KindSource, @@ -51,6 +53,7 @@ func TestAnyResourceV1(t *testing.T) { }, { "destination", + "tf-destination", "my-destination", "googlecloud", model.KindDestination, @@ -65,6 +68,7 @@ func TestAnyResourceV1(t *testing.T) { }, { "processor", + "tf-processor", "my-filter", "filter", model.KindProcessor, @@ -74,6 +78,7 @@ func TestAnyResourceV1(t *testing.T) { }, { "extension", + "tf-extension", "my-extension", "pprof", model.KindExtension, @@ -83,6 +88,7 @@ func TestAnyResourceV1(t *testing.T) { }, { "invalid-kind", + "tf-resource", "my-resource", "resource", model.KindAgent, @@ -92,6 +98,7 @@ func TestAnyResourceV1(t *testing.T) { }, { "valid-processors", + "tf-bundle", "my-bundle", "bundle", model.KindProcessor, @@ -110,7 +117,7 @@ func TestAnyResourceV1(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - _, err := AnyResourceV1(tc.rName, tc.rType, tc.rkind, tc.rParameters, tc.rProcessors) + _, err := AnyResourceV1(tc.id, tc.rName, tc.rType, tc.rkind, tc.rParameters, tc.rProcessors) if tc.expectErr != "" { require.Error(t, err) require.ErrorContains(t, err, tc.expectErr) diff --git a/provider/resource_destination.go b/provider/resource_destination.go index 4d4ff5c..1449a39 100644 --- a/provider/resource_destination.go +++ b/provider/resource_destination.go @@ -22,6 +22,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/observiq/bindplane-op-enterprise/model" "github.com/observiq/terraform-provider-bindplane/client" + "github.com/observiq/terraform-provider-bindplane/internal/component" "github.com/observiq/terraform-provider-bindplane/internal/parameter" "github.com/observiq/terraform-provider-bindplane/internal/resource" ) @@ -87,8 +88,14 @@ func resourceDestinationCreate(d *schema.ResourceData, meta any) error { if c != nil { return fmt.Errorf("destination with name '%s' already exists with id '%s'", name, c.ID()) } + + // If a source does not already exist with this name + // and an ID is not set, generate and ID. + d.SetId(component.NewResourceID()) } + id := d.Id() + parameters := []model.Parameter{} if s := d.Get("parameters_json").(string); s != "" { params, err := parameter.StringToParameter(s) @@ -98,7 +105,7 @@ func resourceDestinationCreate(d *schema.ResourceData, meta any) error { parameters = params } - r, err := resource.AnyResourceV1(name, destType, model.KindDestination, parameters, nil) + r, err := resource.AnyResourceV1(id, name, destType, model.KindDestination, parameters, nil) if err != nil { return err } diff --git a/provider/resource_extension.go b/provider/resource_extension.go index 67bf362..756160f 100644 --- a/provider/resource_extension.go +++ b/provider/resource_extension.go @@ -22,6 +22,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/observiq/bindplane-op-enterprise/model" "github.com/observiq/terraform-provider-bindplane/client" + "github.com/observiq/terraform-provider-bindplane/internal/component" "github.com/observiq/terraform-provider-bindplane/internal/parameter" "github.com/observiq/terraform-provider-bindplane/internal/resource" ) @@ -87,8 +88,14 @@ func resourceExtensionCreate(d *schema.ResourceData, meta any) error { if c != nil { return fmt.Errorf("extension with name '%s' already exists with id '%s'", name, c.ID()) } + + // If a source does not already exist with this name + // and an ID is not set, generate and ID. + d.SetId(component.NewResourceID()) } + id := d.Id() + parameters := []model.Parameter{} if s := d.Get("parameters_json").(string); s != "" { params, err := parameter.StringToParameter(s) @@ -98,7 +105,7 @@ func resourceExtensionCreate(d *schema.ResourceData, meta any) error { parameters = params } - r, err := resource.AnyResourceV1(name, extensionType, model.KindExtension, parameters, nil) + r, err := resource.AnyResourceV1(id, name, extensionType, model.KindExtension, parameters, nil) if err != nil { return err } diff --git a/provider/resource_generic.go b/provider/resource_generic.go index 022de4f..9d2abae 100644 --- a/provider/resource_generic.go +++ b/provider/resource_generic.go @@ -39,27 +39,21 @@ func genericResourceRead(rKind model.Kind, d *schema.ResourceData, meta any) err // A nil return from GenericResource indicates that the resource // did not exist. Terraform read operations should always set the // ID to "" and return a nil error. This will allow Terraform to - // re-create the resource or comfirm that it was deleted. + // re-create the resource or confirm that it was deleted. if g == nil { d.SetId("") return nil } - // Save values returned by bindplane to Terraform's state - - d.SetId(g.ID) - // If the state ID is set but differs from the ID returned by, // bindplane, mark the resource to be re-created by unsetting // the ID. This will cause Terraform to attempt to create the resource // instead of updating it. The creation step will fail because // the resource already exists. This behavior is desirable, it will // prevent Terraform from modifying resources created by other means. - if id := d.Id(); id != "" { - if g.ID != d.Id() { - d.SetId("") - return nil - } + if g.ID != d.Id() { + d.SetId("") + return nil } if err := d.Set("name", g.Name); err != nil { diff --git a/provider/resource_processor.go b/provider/resource_processor.go index d132439..a586b53 100644 --- a/provider/resource_processor.go +++ b/provider/resource_processor.go @@ -22,6 +22,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/observiq/bindplane-op-enterprise/model" "github.com/observiq/terraform-provider-bindplane/client" + "github.com/observiq/terraform-provider-bindplane/internal/component" "github.com/observiq/terraform-provider-bindplane/internal/parameter" "github.com/observiq/terraform-provider-bindplane/internal/resource" ) @@ -87,8 +88,14 @@ func resourceProcessorCreate(d *schema.ResourceData, meta any) error { if c != nil { return fmt.Errorf("processor with name '%s' already exists with id '%s'", name, c.ID()) } + + // If a source does not already exist with this name + // and an ID is not set, generate and ID. + d.SetId(component.NewResourceID()) } + id := d.Id() + parameters := []model.Parameter{} if s := d.Get("parameters_json").(string); s != "" { params, err := parameter.StringToParameter(s) @@ -98,7 +105,7 @@ func resourceProcessorCreate(d *schema.ResourceData, meta any) error { parameters = params } - r, err := resource.AnyResourceV1(name, processorType, model.KindProcessor, parameters, nil) + r, err := resource.AnyResourceV1(id, name, processorType, model.KindProcessor, parameters, nil) if err != nil { return err } diff --git a/provider/resource_processor_bundle.go b/provider/resource_processor_bundle.go index ed7ab3c..87b83e4 100644 --- a/provider/resource_processor_bundle.go +++ b/provider/resource_processor_bundle.go @@ -23,6 +23,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/observiq/bindplane-op-enterprise/model" "github.com/observiq/terraform-provider-bindplane/client" + "github.com/observiq/terraform-provider-bindplane/internal/component" "github.com/observiq/terraform-provider-bindplane/internal/resource" ) @@ -107,8 +108,14 @@ func resourceProcessorBundleCreate(d *schema.ResourceData, meta any) error { if c != nil { return fmt.Errorf("processor with name '%s' already exists with id '%s'", name, c.ID()) } + + // If a source does not already exist with this name + // and an ID is not set, generate and ID. + d.SetId(component.NewResourceID()) } + id := d.Id() + // Using resource configuration instead of []string (names) // to allow for future use of type + parameters_json. processors := []model.ResourceConfiguration{} @@ -131,7 +138,7 @@ func resourceProcessorBundleCreate(d *schema.ResourceData, meta any) error { } } - r, err := resource.AnyResourceV1(name, processorType, model.KindProcessor, nil, processors) + r, err := resource.AnyResourceV1(id, name, processorType, model.KindProcessor, nil, processors) if err != nil { return err } diff --git a/provider/resource_source.go b/provider/resource_source.go index 335d39e..7236cc2 100644 --- a/provider/resource_source.go +++ b/provider/resource_source.go @@ -22,6 +22,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/observiq/bindplane-op-enterprise/model" "github.com/observiq/terraform-provider-bindplane/client" + "github.com/observiq/terraform-provider-bindplane/internal/component" "github.com/observiq/terraform-provider-bindplane/internal/parameter" "github.com/observiq/terraform-provider-bindplane/internal/resource" ) @@ -88,8 +89,14 @@ func resourceSourceCreate(d *schema.ResourceData, meta any) error { if c != nil { return fmt.Errorf("source with name '%s' already exists with id '%s'", name, c.ID()) } + + // If a source does not already exist with this name + // and an ID is not set, generate and ID. + d.SetId(component.NewResourceID()) } + id := d.Id() + parameters := []model.Parameter{} if s := d.Get("parameters_json").(string); s != "" { params, err := parameter.StringToParameter(s) @@ -99,7 +106,7 @@ func resourceSourceCreate(d *schema.ResourceData, meta any) error { parameters = params } - r, err := resource.AnyResourceV1(name, sourceType, model.KindSource, parameters, nil) + r, err := resource.AnyResourceV1(id, name, sourceType, model.KindSource, parameters, nil) if err != nil { return err }