From f6a4884442d413921e1c68176b1dc5bbb6ee7928 Mon Sep 17 00:00:00 2001 From: Heather Lanigan Date: Mon, 22 Apr 2024 14:24:48 -0400 Subject: [PATCH 1/2] Save the secret URI ID not the schema. When interacting with secrets, most do not know there is a schema associated with it. To ensure we don't get mismatches between the two types with terraform doing a string comparision, only save the ID. The risk is with the Access Secret resource where we cannot guarentee how the secret id is provided. Related docs will be updated to be more clear. --- internal/juju/secrets.go | 8 ++++++-- internal/juju/secrets_test.go | 10 ++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/internal/juju/secrets.go b/internal/juju/secrets.go index 7a70bd58..99c1ac2e 100644 --- a/internal/juju/secrets.go +++ b/internal/juju/secrets.go @@ -130,8 +130,12 @@ func (c *secretsClient) CreateSecret(input *CreateSecretInput) (CreateSecretOutp if err != nil { return CreateSecretOutput{}, typedError(err) } + secretURI, err := coresecrets.ParseURI(secretId) + if err != nil { + return CreateSecretOutput{}, typedError(err) + } return CreateSecretOutput{ - SecretId: secretId, + SecretId: secretURI.ID, }, nil } @@ -181,7 +185,7 @@ func (c *secretsClient) ReadSecret(input *ReadSecretInput) (ReadSecretOutput, er applications := getApplicationsFromAccessInfo(results[0].Access) return ReadSecretOutput{ - SecretId: results[0].Metadata.URI.String(), + SecretId: results[0].Metadata.URI.ID, Name: results[0].Metadata.Label, Value: decodedValue, Applications: applications, diff --git a/internal/juju/secrets_test.go b/internal/juju/secrets_test.go index 68c6bb78..f5bfd29f 100644 --- a/internal/juju/secrets_test.go +++ b/internal/juju/secrets_test.go @@ -51,9 +51,12 @@ func (s *SecretSuite) TestCreateSecret() { decodedValue := map[string]string{"key": "value"} encodedValue := map[string]string{"key": base64.StdEncoding.EncodeToString([]byte("value"))} + secretId := "secret:9m4e2mr0ui3e8a215n4g" + secretURI, err := coresecrets.ParseURI(secretId) + s.Require().NoError(err) s.mockSecretClient.EXPECT().CreateSecret( "test-secret", "test info", encodedValue, - ).Return("secret-id", nil).AnyTimes() + ).Return(secretURI.ID, nil).AnyTimes() client := s.getSecretsClient() output, err := client.CreateSecret(&CreateSecretInput{ @@ -65,7 +68,7 @@ func (s *SecretSuite) TestCreateSecret() { s.Require().NoError(err) s.Assert().NotNil(output) - s.Assert().Equal("secret-id", output.SecretId) + s.Assert().Equal(secretURI.ID, output.SecretId) } func (s *SecretSuite) TestCreateSecretError() { @@ -114,6 +117,7 @@ func (s *SecretSuite) TestReadSecret() { ).Return([]apisecrets.SecretDetails{ { Metadata: coresecrets.SecretMetadata{ + URI: secretURI, Version: 1, }, Revisions: []coresecrets.SecretRevisionMetadata{ @@ -204,6 +208,7 @@ func (s *SecretSuite) TestUpdateSecretWithRenaming() { ).Return([]apisecrets.SecretDetails{ { Metadata: coresecrets.SecretMetadata{ + URI: secretURI, Version: 1, }, Revisions: []coresecrets.SecretRevisionMetadata{ @@ -259,6 +264,7 @@ func (s *SecretSuite) TestUpdateSecret() { ).Return([]apisecrets.SecretDetails{ { Metadata: coresecrets.SecretMetadata{ + URI: secretURI, Version: 1, Description: secretInfo, }, From 10a8c9bffe1649ff12b04027d93555e51df0a3bd Mon Sep 17 00:00:00 2001 From: Heather Lanigan Date: Mon, 22 Apr 2024 15:20:54 -0400 Subject: [PATCH 2/2] Implement full ImportState for secret and access secret resources. Do not use ImportStatePassthroughID. This allows for importing these resources by the secret name rather than requiring the secret URI. It will be the only time we Read one of the resources by name rather than ID. Terraform errors if the ID attribute name provided in the ImportStateRequest is not written to state at some point. In state, this will be modelname:secretname. Updating docs with new examples and updated schema information. --- docs/resources/access_secret.md | 8 +- docs/resources/secret.md | 28 +++--- examples/resources/juju_secret/import.sh | 12 +-- examples/resources/juju_secret/resource.tf | 13 ++- .../resources/juju_secret_access/import.sh | 12 +-- .../resources/juju_secret_access/resource.tf | 15 ++- internal/provider/resource_access_secret.go | 76 +++++++++++++-- .../provider/resource_access_secret_test.go | 32 +++++++ internal/provider/resource_secret.go | 92 +++++++++++++++++-- internal/provider/resource_secret_test.go | 8 ++ 10 files changed, 238 insertions(+), 58 deletions(-) diff --git a/docs/resources/access_secret.md b/docs/resources/access_secret.md index 8258da9d..56e9678b 100644 --- a/docs/resources/access_secret.md +++ b/docs/resources/access_secret.md @@ -17,6 +17,10 @@ A resource that represents a Juju secret access. ### Required -- `applications` (List of String) The list of applications to which the secret is granted or revoked. +- `applications` (List of String) The list of applications to which the secret is granted. - `model` (String) The model in which the secret belongs. -- `secret_id` (String) The ID of the secret. +- `secret_id` (String) The ID of the secret. E.g. coj8mulh8b41e8nv6p90 + +### Read-Only + +- `id` (String) The ID of the secret. Used for terraform import. diff --git a/docs/resources/secret.md b/docs/resources/secret.md index 35772eaf..12c01ef1 100644 --- a/docs/resources/secret.md +++ b/docs/resources/secret.md @@ -13,15 +13,24 @@ A resource that represents a Juju secret. ## Example Usage ```terraform -resource "juju_secret" "this" { +resource "juju_secret" "my-secret" { model = juju_model.development.name - name = "this_secret_name" + name = "my_secret_name" value = { key1 = "value1" key2 = "value2" } info = "This is the secret" } + +resource "juju_application" "my-application" { + # + config = { + # Reference my-secret within the plan by using the secret_id + secret = juju_secret.my-secret.secret_id + } + # +} ``` @@ -39,21 +48,14 @@ resource "juju_secret" "this" { ### Read-Only -- `secret_id` (String) The ID of the secret. +- `id` (String) The ID of the secret. Used for terraform import. +- `secret_id` (String) The ID of the secret. E.g. coj8mulh8b41e8nv6p90 ## Import Import is supported using the following syntax: ```shell -# Secrets can be imported by using the URI as in the juju show-secrets output. -# Example: -# $juju show-secret secret-name -# coh2uo2ji6m0ue9a7tj0: -# revision: 1 -# owner: -# name: secret-name -# created: 2024-04-19T08:46:25Z -# updated: 2024-04-19T08:46:25Z -$ terraform import juju_secret.secret-name coh2uo2ji6m0ue9a7tj0 +# Secrets can be imported by using the model and secret names. +$ terraform import juju_secret.secret-name testmodel:secret-name ``` diff --git a/examples/resources/juju_secret/import.sh b/examples/resources/juju_secret/import.sh index c06fe998..1abb557a 100644 --- a/examples/resources/juju_secret/import.sh +++ b/examples/resources/juju_secret/import.sh @@ -1,10 +1,2 @@ -# Secrets can be imported by using the URI as in the juju show-secrets output. -# Example: -# $juju show-secret secret-name -# coh2uo2ji6m0ue9a7tj0: -# revision: 1 -# owner: -# name: secret-name -# created: 2024-04-19T08:46:25Z -# updated: 2024-04-19T08:46:25Z -$ terraform import juju_secret.secret-name coh2uo2ji6m0ue9a7tj0 \ No newline at end of file +# Secrets can be imported by using the model and secret names. +$ terraform import juju_secret.secret-name testmodel:secret-name \ No newline at end of file diff --git a/examples/resources/juju_secret/resource.tf b/examples/resources/juju_secret/resource.tf index d1b5ad48..a8cb3c0e 100644 --- a/examples/resources/juju_secret/resource.tf +++ b/examples/resources/juju_secret/resource.tf @@ -1,9 +1,18 @@ -resource "juju_secret" "this" { +resource "juju_secret" "my-secret" { model = juju_model.development.name - name = "this_secret_name" + name = "my_secret_name" value = { key1 = "value1" key2 = "value2" } info = "This is the secret" +} + +resource "juju_application" "my-application" { + # + config = { + # Reference my-secret within the plan by using the secret_id + secret = juju_secret.my-secret.secret_id + } + # } \ No newline at end of file diff --git a/examples/resources/juju_secret_access/import.sh b/examples/resources/juju_secret_access/import.sh index 72330b2c..de66d236 100644 --- a/examples/resources/juju_secret_access/import.sh +++ b/examples/resources/juju_secret_access/import.sh @@ -1,10 +1,2 @@ -# Secret access can be imported by using the URI as in the juju show-secrets output. -# Example: -# $juju show-secret secret-name -# coh2uo2ji6m0ue9a7tj0: -# revision: 1 -# owner: -# name: secret-name -# created: 2024-04-19T08:46:25Z -# updated: 2024-04-19T08:46:25Z -$ terraform import juju_access_secret.access-secret-name coh2uo2ji6m0ue9a7tj0 \ No newline at end of file +# Secret access can be imported by using the model and secret names. +$ terraform import juju_access_secret.access-secret-name modelname:secret-name \ No newline at end of file diff --git a/examples/resources/juju_secret_access/resource.tf b/examples/resources/juju_secret_access/resource.tf index 9e05c759..06fc2d7a 100644 --- a/examples/resources/juju_secret_access/resource.tf +++ b/examples/resources/juju_secret_access/resource.tf @@ -1,7 +1,18 @@ -resource "juju_access_secret" "this" { +resource "juju_secret" "my-secret" { + model = juju_model.development.name + name = "my_secret_name" + value = { + key1 = "value1" + key2 = "value2" + } + info = "This is the secret" +} + +resource "juju_access_secret" "my-secret-access" { model = juju_model.development.name applications = [ juju_application.app.name, juju_application.app2.name ] - secret_id = juju_secret.that.secret_id + # Use the secret_id from your secret resource or data source. + secret_id = juju_secret.my-secret.secret_id } \ No newline at end of file diff --git a/internal/provider/resource_access_secret.go b/internal/provider/resource_access_secret.go index 58ee734d..045211cd 100644 --- a/internal/provider/resource_access_secret.go +++ b/internal/provider/resource_access_secret.go @@ -6,9 +6,9 @@ package provider import ( "context" "fmt" + "strings" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" @@ -43,10 +43,58 @@ type accessSecretResourceModel struct { SecretId types.String `tfsdk:"secret_id"` // Applications is the list of applications to which the secret is granted or revoked. Applications types.List `tfsdk:"applications"` + // ID is used during terraform import. + ID types.String `tfsdk:"id"` } +// ImportState reads the secret based on the model name and secret name to be +// imported into terraform. func (s *accessSecretResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) + // Prevent panic if the provider has not been configured. + if s.client == nil { + addClientNotConfiguredError(&resp.Diagnostics, "access secret", "import") + return + } + // model:name + parts := strings.Split(req.ID, ":") + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: :. Got: %q", req.ID), + ) + return + } + modelName := parts[0] + secretName := parts[1] + + readSecretOutput, err := s.client.Secrets.ReadSecret(&juju.ReadSecretInput{ + ModelName: modelName, + Name: &secretName, + }) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read secret for import, got error: %s", err)) + return + } + + // Save the secret access details into the Terraform state + state := accessSecretResourceModel{ + Model: types.StringValue(modelName), + SecretId: types.StringValue(readSecretOutput.SecretId), + ID: types.StringValue(newSecretID(modelName, readSecretOutput.SecretId)), + } + + // Save the secret details into the Terraform state + secretApplications, errDiag := types.ListValueFrom(ctx, types.StringType, readSecretOutput.Applications) + resp.Diagnostics.Append(errDiag...) + if resp.Diagnostics.HasError() { + return + } + state.Applications = secretApplications + + // Save state into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + + s.trace(fmt.Sprintf("import access secret resource %q", state.SecretId)) } func (s *accessSecretResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { @@ -66,17 +114,24 @@ func (s *accessSecretResource) Schema(_ context.Context, req resource.SchemaRequ }, }, "secret_id": schema.StringAttribute{ - Description: "The ID of the secret.", + Description: "The ID of the secret. E.g. coj8mulh8b41e8nv6p90", Required: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, }, "applications": schema.ListAttribute{ - Description: "The list of applications to which the secret is granted or revoked.", + Description: "The list of applications to which the secret is granted.", Required: true, ElementType: types.StringType, }, + "id": schema.StringAttribute{ + Description: "The ID of the secret. Used for terraform import.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, }, } } @@ -98,7 +153,7 @@ func (s *accessSecretResource) Configure(ctx context.Context, req resource.Confi } s.client = client // Create the local logging subsystem here, using the TF context when creating it. - s.subCtx = tflog.NewSubsystem(ctx, LogResourceSecret) + s.subCtx = tflog.NewSubsystem(ctx, LogResourceAccessSecret) } // Create is called when the resource is being created. @@ -131,9 +186,10 @@ func (s *accessSecretResource) Create(ctx context.Context, req resource.CreateRe } // Save plan into Terraform state + plan.ID = types.StringValue(newSecretID(plan.Model.ValueString(), plan.SecretId.ValueString())) resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) - s.trace(fmt.Sprintf("grant secret access to %q", plan.SecretId)) + s.trace(fmt.Sprintf("grant secret access to %s", plan.SecretId)) } // Read is called when the resource is being read. @@ -169,10 +225,12 @@ func (s *accessSecretResource) Read(ctx context.Context, req resource.ReadReques } state.Applications = secretApplications + state.ID = types.StringValue(newSecretID(state.Model.ValueString(), readSecretOutput.SecretId)) + // Save state into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) - s.trace(fmt.Sprintf("read secret access %q", state.SecretId)) + s.trace(fmt.Sprintf("read secret access %s", state.SecretId)) } // Update is called when the resource is being updated. @@ -264,7 +322,7 @@ func (s *accessSecretResource) Update(ctx context.Context, req resource.UpdateRe // Save updated data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) - s.trace(fmt.Sprintf("update secret access %q", state.SecretId)) + s.trace(fmt.Sprintf("update secret access %s", state.SecretId)) } // Delete is called when the resource is being deleted. @@ -310,7 +368,7 @@ func (s *accessSecretResource) Delete(ctx context.Context, req resource.DeleteRe // Save updated data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) - s.trace(fmt.Sprintf("revoke secret access %q", state.SecretId)) + s.trace(fmt.Sprintf("revoke secret access %s", state.SecretId)) } func (s *accessSecretResource) trace(msg string, additionalFields ...map[string]interface{}) { diff --git a/internal/provider/resource_access_secret_test.go b/internal/provider/resource_access_secret_test.go index 30b9bbe2..606765cb 100644 --- a/internal/provider/resource_access_secret_test.go +++ b/internal/provider/resource_access_secret_test.go @@ -4,6 +4,7 @@ package provider import ( + "fmt" "os" "testing" @@ -56,6 +57,37 @@ func TestAcc_ResourceAccessSecret_GrantRevoke(t *testing.T) { }) } +func TestAcc_ResourceAccessSecret_Import(t *testing.T) { + agentVersion := os.Getenv(TestJujuAgentVersion) + if agentVersion == "" { + t.Errorf("%s is not set", TestJujuAgentVersion) + } else if internaltesting.CompareVersions(agentVersion, "3.3.0") < 0 { + t.Skipf("%s is not set or is below 3.3.0", TestJujuAgentVersion) + } + + modelName := acctest.RandomWithPrefix("tf-test-model") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: frameworkProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccResourceSecretWithAccess(modelName, true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("juju_access_secret.test_access_secret", "model", modelName), + resource.TestCheckResourceAttr("juju_access_secret.test_access_secret", "applications.0", "jul"), + ), + }, + { + ImportStateVerify: true, + ImportState: true, + ImportStateId: fmt.Sprintf("%s:test_secret_name", modelName), + ResourceName: "juju_access_secret.test_access_secret", + }, + }, + }) +} + func testAccResourceSecretWithAccess(modelName string, allApplicationAccess bool) string { return internaltesting.GetStringFromTemplateWithData( "testAccResourceSecret", diff --git a/internal/provider/resource_secret.go b/internal/provider/resource_secret.go index 5b428623..f82d2c2d 100644 --- a/internal/provider/resource_secret.go +++ b/internal/provider/resource_secret.go @@ -6,14 +6,15 @@ package provider import ( "context" "fmt" + "strings" - "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/juju/terraform-provider-juju/internal/juju" ) @@ -45,10 +46,62 @@ type secretResourceModel struct { SecretId types.String `tfsdk:"secret_id"` // Info is the description of the secret. This attribute is optional for all actions. Info types.String `tfsdk:"info"` + // ID is used during terraform import. + ID types.String `tfsdk:"id"` } +// ImportState reads the secret based on the model name and secret name to be +// imported into terraform. func (s *secretResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) + // Prevent panic if the provider has not been configured. + if s.client == nil { + addClientNotConfiguredError(&resp.Diagnostics, "secret", "import") + return + } + + // model:name + parts := strings.Split(req.ID, ":") + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: :. Got: %q", req.ID), + ) + return + } + modelName := parts[0] + secretName := parts[1] + + readSecretOutput, err := s.client.Secrets.ReadSecret(&juju.ReadSecretInput{ + ModelName: modelName, + Name: &secretName, + }) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read secret for import, got error: %s", err)) + return + } + + // Save the secret details into the Terraform state + state := secretResourceModel{ + Model: types.StringValue(modelName), + Name: types.StringValue(readSecretOutput.Name), + SecretId: types.StringValue(readSecretOutput.SecretId), + } + + if readSecretOutput.Info != "" { + state.Info = types.StringValue(readSecretOutput.Info) + } + + secretValue, errDiag := types.MapValueFrom(ctx, types.StringType, readSecretOutput.Value) + resp.Diagnostics.Append(errDiag...) + if resp.Diagnostics.HasError() { + return + } + state.Value = secretValue + + // Save state into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + + s.trace(fmt.Sprintf("import secret resource %q", state.SecretId)) } func (s *secretResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { @@ -77,7 +130,7 @@ func (s *secretResource) Schema(_ context.Context, req resource.SchemaRequest, r Sensitive: true, }, "secret_id": schema.StringAttribute{ - Description: "The ID of the secret.", + Description: "The ID of the secret. E.g. coj8mulh8b41e8nv6p90", Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), @@ -87,6 +140,13 @@ func (s *secretResource) Schema(_ context.Context, req resource.SchemaRequest, r Description: "The description of the secret.", Optional: true, }, + "id": schema.StringAttribute{ + Description: "The ID of the secret. Used for terraform import.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, }, } } @@ -144,11 +204,20 @@ func (s *secretResource) Create(ctx context.Context, req resource.CreateRequest, } plan.SecretId = types.StringValue(createSecretOutput.SecretId) - + plan.ID = types.StringValue(newSecretID(plan.Model.ValueString(), plan.SecretId.ValueString())) + s.trace(fmt.Sprintf("saving secret resource %q", plan.SecretId.ValueString()), + map[string]interface{}{ + "secretID": plan.SecretId.ValueString(), + "name": plan.Name.ValueString(), + "model": plan.Model.ValueString(), + "info": plan.Info.ValueString(), + "values": plan.Value.String(), + "id": plan.ID.ValueString(), + }) // Save plan into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) - s.trace(fmt.Sprintf("created secret resource %q", plan.SecretId)) + s.trace(fmt.Sprintf("created secret resource %s", plan.SecretId)) } // Read reads the details of a secret in the Juju model. @@ -172,8 +241,6 @@ func (s *secretResource) Read(ctx context.Context, req resource.ReadRequest, res readSecretOutput, err := s.client.Secrets.ReadSecret(&juju.ReadSecretInput{ SecretId: state.SecretId.ValueString(), ModelName: state.Model.ValueString(), - Name: state.Name.ValueStringPointer(), - Revision: nil, }) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read secret, got error: %s", err)) @@ -187,6 +254,7 @@ func (s *secretResource) Read(ctx context.Context, req resource.ReadRequest, res if !state.Info.IsNull() { state.Info = types.StringValue(readSecretOutput.Info) } + state.ID = types.StringValue(newSecretID(state.Model.ValueString(), readSecretOutput.SecretId)) secretValue, errDiag := types.MapValueFrom(ctx, types.StringType, readSecretOutput.Value) resp.Diagnostics.Append(errDiag...) @@ -198,7 +266,7 @@ func (s *secretResource) Read(ctx context.Context, req resource.ReadRequest, res // Save state into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) - s.trace(fmt.Sprintf("read secret resource %q", state.SecretId)) + s.trace(fmt.Sprintf("read secret resource %s", state.SecretId)) } // Update updates the details of a secret in the Juju model. @@ -270,7 +338,7 @@ func (s *secretResource) Update(ctx context.Context, req resource.UpdateRequest, // Save updated data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) - s.trace(fmt.Sprintf("updated secret resource %q", state.SecretId)) + s.trace(fmt.Sprintf("updated secret resource %s", state.SecretId)) } // Delete removes a secret from the Juju model. @@ -300,7 +368,7 @@ func (s *secretResource) Delete(ctx context.Context, req resource.DeleteRequest, return } - s.trace(fmt.Sprintf("deleted secret resource %q", state.SecretId)) + s.trace(fmt.Sprintf("deleted secret resource %s", state.SecretId)) } func (s *secretResource) trace(msg string, additionalFields ...map[string]interface{}) { @@ -309,3 +377,7 @@ func (s *secretResource) trace(msg string, additionalFields ...map[string]interf } tflog.SubsystemTrace(s.subCtx, LogResourceSecret, msg, additionalFields...) } + +func newSecretID(model, secret string) string { + return fmt.Sprintf("%s:%s", model, secret) +} diff --git a/internal/provider/resource_secret_test.go b/internal/provider/resource_secret_test.go index fd082560..856ef4a2 100644 --- a/internal/provider/resource_secret_test.go +++ b/internal/provider/resource_secret_test.go @@ -4,11 +4,13 @@ package provider import ( + "fmt" "os" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + internaltesting "github.com/juju/terraform-provider-juju/internal/testing" ) @@ -74,6 +76,12 @@ func TestAcc_ResourceSecret_CreateWithInfo(t *testing.T) { resource.TestCheckResourceAttr("juju_secret."+secretName, "value.key2", "value2"), ), }, + { + ImportStateVerify: true, + ImportState: true, + ImportStateId: fmt.Sprintf("%s:%s", modelName, secretName), + ResourceName: "juju_secret." + secretName, + }, }, }) }