Skip to content

Commit

Permalink
Add secret data source and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Aflynn50 committed Apr 18, 2024
1 parent d27c343 commit 1a05797
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 0 deletions.
1 change: 1 addition & 0 deletions .github/workflows/canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ jobs:
run: |
CONTROLLER=$(juju whoami --format yaml | yq .controller)
echo "JUJU_AGENT_VERSION=$(juju show-controller | yq .$CONTROLLER.details.agent-version |tr -d '"')" >> $GITHUB_ENV
echo "JUJU_CONTROLLER_ADDRESSES=$(juju show-controller | yq .$CONTROLLER.details.api-endpoints | yq -r '. | join(",")')" >> $GITHUB_ENV
echo "JUJU_USERNAME=$(juju show-controller | yq .$CONTROLLER.account.user)" >> $GITHUB_ENV
echo "JUJU_PASSWORD=$(cat ~/.local/share/juju/accounts.yaml | yq .controllers.$CONTROLLER.password)" >> $GITHUB_ENV
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/k8s_tunnel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ jobs:
run: |
echo "Determine Juju details"
CONTROLLER=$(juju whoami --format yaml | yq .controller)
JUJU_AGENT_VERSION=$(juju show-controller | yq .$CONTROLLER.details.agent-version |tr -d '"')
JUJU_USERNAME=$(juju show-controller | yq .$CONTROLLER.account.user)
JUJU_PASSWORD=$(cat ~/.local/share/juju/accounts.yaml | yq .controllers.$CONTROLLER.password)
JUJU_CA_CERT=$(juju show-controller | yq .$CONTROLLER.details.ca-cert | sed ':a;N;$!ba;s/\n/\\n/g')
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test_add_machine.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ jobs:
run: |
CONTROLLER=$(juju whoami --format yaml | yq .controller)
echo "JUJU_AGENT_VERSION=$(juju show-controller | yq .$CONTROLLER.details.agent-version |tr -d '"')" >> $GITHUB_ENV
echo "JUJU_CONTROLLER_ADDRESSES=$(juju show-controller | yq .$CONTROLLER.details.api-endpoints | yq -r '. | join(",")')" >> $GITHUB_ENV
echo "JUJU_USERNAME=$(juju show-controller | yq .$CONTROLLER.account.user)" >> $GITHUB_ENV
echo "JUJU_PASSWORD=$(cat ~/.local/share/juju/accounts.yaml | yq .controllers.$CONTROLLER.password)" >> $GITHUB_ENV
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test_integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ jobs:
run: |
CONTROLLER=$(juju whoami --format yaml | yq .controller)
echo "JUJU_AGENT_VERSION=$(juju show-controller | yq .$CONTROLLER.details.agent-version |tr -d '"')" >> $GITHUB_ENV
echo "JUJU_CONTROLLER_ADDRESSES=$(juju show-controller | yq .$CONTROLLER.details.api-endpoints | yq -r '. | join(",")')" >> $GITHUB_ENV
echo "JUJU_USERNAME=$(juju show-controller | yq .$CONTROLLER.account.user)" >> $GITHUB_ENV
echo "JUJU_PASSWORD=$(cat ~/.local/share/juju/accounts.yaml | yq .controllers.$CONTROLLER.password)" >> $GITHUB_ENV
Expand Down
25 changes: 25 additions & 0 deletions docs/data-sources/secret.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "juju_secret Data Source - terraform-provider-juju"
subcategory: ""
description: |-
A data source representing a Juju Secret.
---

# juju_secret (Data Source)

A data source representing a Juju Secret.



<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `model` (String) The name of the model containing the secret.
- `name` (String) The name of the secret.

### Read-Only

- `secret_id` (String) The ID of the secret.
4 changes: 4 additions & 0 deletions examples/data-sources/juju_secret/data-souce.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
data "juju_secret" "this" {
model = "model-name"
name = "secret-name"
}
134 changes: 134 additions & 0 deletions internal/provider/data_source_secrets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright 2024 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package provider

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/juju/terraform-provider-juju/internal/juju"
)

// Ensure provider defined types fully satisfy framework interfaces.
var _ datasource.DataSourceWithConfigure = &secretDataSource{}

func NewSecretDataSource() datasource.DataSource {
return &secretDataSource{}
}

type secretDataSource struct {
client *juju.Client

// context for the logging subsystem.
subCtx context.Context
}

// secretDataSourceModel is the juju data stored by terraform.
// tfsdk must match secret data source schema attribute names.
type secretDataSourceModel struct {
// Model to which the secret belongs.
Model types.String `tfsdk:"model"`
// Name of the secret to be updated or removed.
Name types.String `tfsdk:"name"`
// SecretId is the ID of the secret.
SecretId types.String `tfsdk:"secret_id"`
}

// Metadata returns the full data source name as used in terraform plans.
func (d *secretDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_secret"
}

// Schema returns the schema for the model data source.
func (d *secretDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "A data source representing a Juju Secret.",
Attributes: map[string]schema.Attribute{
"model": schema.StringAttribute{
Description: "The name of the model containing the secret.",
Required: true,
},
"name": schema.StringAttribute{
Description: "The name of the secret.",
Required: true,
},
"secret_id": schema.StringAttribute{
Description: "The ID of the secret.",
Computed: true,
},
},
}
}

// Configure enables provider-level data or clients to be set in the
// provider-defined DataSource type. It is separately executed for each
// ReadDataSource RPC.
func (d *secretDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
// Prevent panic if the provider has not been configured.
if req.ProviderData == nil {
return
}

client, ok := req.ProviderData.(*juju.Client)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)
return
}

d.client = client
d.subCtx = tflog.NewSubsystem(ctx, LogDataSourceSecret)
}

// Read is called when the provider must read data source values in
// order to update state. Config values should be read from the
// ReadRequest and new state values set on the ReadResponse.
func (d *secretDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
// Prevent panic if the provider has not been configured.
if d.client == nil {
addDSClientNotConfiguredError(&resp.Diagnostics, "secret")
return
}

var data secretDataSourceModel

// Read Terraform configuration state into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

readSecretOutput, err := d.client.Secrets.ReadSecret(&juju.ReadSecretInput{
ModelName: data.Model.ValueString(),
Name: data.Name.ValueStringPointer(),
Revision: nil,
})
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read secret, got error: %s", err))
return
}
d.trace(fmt.Sprintf("read secret data source %q", data.SecretId))

data.SecretId = types.StringValue(readSecretOutput.SecretId)

// Save state into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (d *secretDataSource) trace(msg string, additionalFields ...map[string]interface{}) {
if d.subCtx == nil {
return
}

//SubsystemTrace(subCtx, "datasource-secret", "hello, world", map[string]interface{}{"foo": 123})
// Output:
// {"@level":"trace","@message":"hello, world","@module":"juju.datasource-secret","foo":123}
tflog.SubsystemTrace(d.subCtx, LogDataSourceSecret, msg, additionalFields...)
}
71 changes: 71 additions & 0 deletions internal/provider/data_source_secrets_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2024 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

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"
)

func TestAcc_DataSourceSecret(t *testing.T) {
if os.Getenv("JUJU_AGENT_VERSION") == "" || internaltesting.CompareVersions(os.Getenv("JUJU_AGENT_VERSION"), "3.3.0") < 0 {
t.Skip("JUJU_AGENT_VERSION is not set or is below 3.3.0")
}
modelName := acctest.RandomWithPrefix("tf-datasource-secret-test-model")
// ...-test-[0-9]+ is not a valid secret name, need to remove the dash before numbers
secretName := fmt.Sprintf("tf-datasource-secret-test%d", acctest.RandInt())
secretValue := map[string]string{
"key1": "value1",
"key2": "value2",
}

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: frameworkProviderFactories,
Steps: []resource.TestStep{
{
Config: testAccDataSourceSecret(modelName, secretName, secretValue),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.juju_secret.secret_data_source", "model", modelName),
resource.TestCheckResourceAttr("data.juju_secret.secret_data_source", "name", secretName),
resource.TestCheckResourceAttrPair("data.juju_secret.secret_data_source", "secretID", "juju_secret.secret_resource", "secretID"),
),
},
},
})
}

func testAccDataSourceSecret(modelName, secretName string, secretValue map[string]string) string {
return internaltesting.GetStringFromTemplateWithData(
"testAccResourceSecret",
`
resource "juju_model" "{{.ModelName}}" {
name = "{{.ModelName}}"
}
resource "juju_secret" "secret_resource" {
model = juju_model.{{.ModelName}}.name
name = "{{.SecretName}}"
value = {
{{- range $key, $value := .SecretValue }}
"{{$key}}" = "{{$value}}"
{{- end }}
}
}
data "juju_secret" "secret_data_source" {
name = juju_secret.secret_resource.name
model = juju_model.{{.ModelName}}.name
}
`, internaltesting.TemplateData{
"ModelName": modelName,
"SecretName": secretName,
"SecretValue": secretValue,
})
}
1 change: 1 addition & 0 deletions internal/provider/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const (
LogDataSourceMachine = "datasource-machine"
LogDataSourceModel = "datasource-model"
LogDataSourceOffer = "datasource-offer"
LogDataSourceSecret = "datasource-secret"

LogResourceApplication = "resource-application"
LogResourceAccessModel = "resource-assess-model"
Expand Down
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ func (p *jujuProvider) DataSources(_ context.Context) []func() datasource.DataSo
func() datasource.DataSource { return NewMachineDataSource() },
func() datasource.DataSource { return NewModelDataSource() },
func() datasource.DataSource { return NewOfferDataSource() },
func() datasource.DataSource { return NewSecretDataSource() },
}
}

Expand Down

0 comments on commit 1a05797

Please sign in to comment.