Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import secret and access secret resources #467

Merged
merged 2 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions docs/resources/access_secret.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
28 changes: 15 additions & 13 deletions docs/resources/secret.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
#
}
```

<!-- schema generated by tfplugindocs -->
Expand All @@ -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: <model>
# 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
```
12 changes: 2 additions & 10 deletions examples/resources/juju_secret/import.sh
Original file line number Diff line number Diff line change
@@ -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: <model>
# 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
13 changes: 11 additions & 2 deletions examples/resources/juju_secret/resource.tf
Original file line number Diff line number Diff line change
@@ -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
}
#
}
12 changes: 2 additions & 10 deletions examples/resources/juju_secret_access/import.sh
Original file line number Diff line number Diff line change
@@ -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: <model>
# name: secret-name
# created: 2024-04-19T08:46:25Z
# updated: 2024-04-19T08:46:25Z
$ terraform import juju_access_secret.access-secret-name coh2uo2ji6m0ue9a7tj0
# Secret access can be imported by using the model and secret names.
$ terraform import juju_access_secret.access-secret-name modelname:secret-name
15 changes: 13 additions & 2 deletions examples/resources/juju_secret_access/resource.tf
Original file line number Diff line number Diff line change
@@ -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
}
8 changes: 6 additions & 2 deletions internal/juju/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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,
Expand Down
10 changes: 8 additions & 2 deletions internal/juju/secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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() {
Expand Down Expand Up @@ -114,6 +117,7 @@ func (s *SecretSuite) TestReadSecret() {
).Return([]apisecrets.SecretDetails{
{
Metadata: coresecrets.SecretMetadata{
URI: secretURI,
Version: 1,
},
Revisions: []coresecrets.SecretRevisionMetadata{
Expand Down Expand Up @@ -204,6 +208,7 @@ func (s *SecretSuite) TestUpdateSecretWithRenaming() {
).Return([]apisecrets.SecretDetails{
{
Metadata: coresecrets.SecretMetadata{
URI: secretURI,
Version: 1,
},
Revisions: []coresecrets.SecretRevisionMetadata{
Expand Down Expand Up @@ -259,6 +264,7 @@ func (s *SecretSuite) TestUpdateSecret() {
).Return([]apisecrets.SecretDetails{
{
Metadata: coresecrets.SecretMetadata{
URI: secretURI,
Version: 1,
Description: secretInfo,
},
Expand Down
76 changes: 67 additions & 9 deletions internal/provider/resource_access_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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: <modelname>:<secretname>. 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) {
Expand All @@ -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(),
},
},
},
}
}
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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{}) {
Expand Down
Loading
Loading