Skip to content

Commit

Permalink
Implement internal Juju secrets add, update, and remove functionality
Browse files Browse the repository at this point in the history
This commit introduces several changes to the Juju client in the `internal/juju/client.go` file.
It includes the implementation of methods for adding, updating, and removing secrets. Additionally,

Furthermore, the commit includes changes to the `secret.go` file, introducing new types for managinng secrets.
It also includes changes to the `interfaces.go` file, defining new interfaces for the Juju client API.
  • Loading branch information
anvial committed Apr 1, 2024
1 parent 1e971f1 commit eb0e7f4
Show file tree
Hide file tree
Showing 6 changed files with 324 additions and 81 deletions.
12 changes: 12 additions & 0 deletions internal/juju/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import (
apiapplication "github.com/juju/juju/api/client/application"
apiclient "github.com/juju/juju/api/client/client"
apiresources "github.com/juju/juju/api/client/resources"
apisecrets "github.com/juju/juju/api/client/secrets"
apicommoncharm "github.com/juju/juju/api/common/charm"
"github.com/juju/juju/core/constraints"
"github.com/juju/juju/core/model"
"github.com/juju/juju/core/resources"
"github.com/juju/juju/core/secrets"
"github.com/juju/juju/rpc/params"
"github.com/juju/names/v4"
)
Expand Down Expand Up @@ -62,3 +64,13 @@ type ResourceAPIClient interface {
AddPendingResources(args apiresources.AddPendingResourcesArgs) ([]string, error)
ListResources(applications []string) ([]resources.ApplicationResources, error)
}

type SecretAPIClient interface {
CreateSecret(name, description string, data map[string]string) (string, error)
ListSecrets(reveal bool, filter secrets.Filter) ([]apisecrets.SecretDetails, error)
UpdateSecret(
uri *secrets.URI, name string, autoPrune *bool,
newName string, description string, data map[string]string,
) error
RemoveSecret(uri *secrets.URI, name string, revision *int) error
}
87 changes: 85 additions & 2 deletions internal/juju/mock_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion internal/juju/package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@

package juju_test

//go:generate go run go.uber.org/mock/mockgen -package juju -destination mock_test.go github.com/juju/terraform-provider-juju/internal/juju SharedClient,ClientAPIClient,ApplicationAPIClient,ModelConfigAPIClient,ResourceAPIClient
//go:generate go run go.uber.org/mock/mockgen -package juju -destination mock_test.go github.com/juju/terraform-provider-juju/internal/juju SharedClient,ClientAPIClient,ApplicationAPIClient,ModelConfigAPIClient,ResourceAPIClient,SecretAPIClient
//go:generate go run go.uber.org/mock/mockgen -package juju -destination jujuapi_mock_test.go github.com/juju/juju/api Connection
139 changes: 139 additions & 0 deletions internal/juju/secrets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright 2024 Canonical Ltd.
// Licensed under the Apache License, Version 2.0, see LICENCE file for details.

package juju

import (
"errors"

"github.com/juju/juju/api"
apisecrets "github.com/juju/juju/api/client/secrets"
coresecrets "github.com/juju/juju/core/secrets"
)

type secretsClient struct {
SharedClient

getSecretAPIClient func(connection api.Connection) SecretAPIClient
}

type CreateSecretInput struct {
ModelName string
Name string
Value map[string]string
Info string
}

type CreateSecretOutput struct {
SecretId string
}

type ReadSecretInput struct {
ModelName string
Name string
}

type ReadSecretOutput struct {
Value map[string]string
}

type UpdateSecretInput struct {
ModelName string
Name string
Value map[string]string
Info string
}

type DeleteSecretInput struct {
ModelName string
Name string
}

func newSecretsClient(sc SharedClient) *secretsClient {
return &secretsClient{
SharedClient: sc,
getSecretAPIClient: func(connection api.Connection) SecretAPIClient {
return apisecrets.NewClient(connection)
},
}
}

// CreateSecret creates a new secret.
func (c *secretsClient) CreateSecret(input *CreateSecretInput) (*CreateSecretOutput, error) {
conn, err := c.GetConnection(&input.ModelName)
if err != nil {
return nil, err
}
defer func() { _ = conn.Close() }()

secretAPIClient := c.getSecretAPIClient(conn)
secretId, err := secretAPIClient.CreateSecret(input.Name, input.Info, input.Value)
if err != nil {
return nil, err
}
return &CreateSecretOutput{
SecretId: secretId,
}, nil
}

// ReadSecret reads a secret.
func (c *secretsClient) ReadSecret(input *ReadSecretInput) (*ReadSecretOutput, error) {
conn, err := c.GetConnection(&input.ModelName)
if err != nil {
return nil, err
}
defer func() { _ = conn.Close() }()

secretAPIClient := c.getSecretAPIClient(conn)
results, err := secretAPIClient.ListSecrets(true, coresecrets.Filter{Label: &input.Name})
if err != nil {
return nil, err
}
if results[0].Error != "" {
return nil, errors.New(results[0].Error)
}

value, err := results[0].Value.Values()
if err != nil {
return nil, err
}
return &ReadSecretOutput{
Value: value,
}, nil
}

// UpdateSecret updates a secret.
func (c *secretsClient) UpdateSecret(input *UpdateSecretInput) error {
conn, err := c.GetConnection(&input.ModelName)
if err != nil {
return err
}
defer func() { _ = conn.Close() }()

secretAPIClient := c.getSecretAPIClient(conn)
// TODO: find a way to model auto-prune option
// TODO: looks like we can change the secret name, but not sure if that's a good idea
err = secretAPIClient.UpdateSecret(nil, input.Name, nil, input.Name, input.Info, input.Value)
if err != nil {
return err
}

return nil
}

// DeleteSecret deletes a secret.
func (c *secretsClient) DeleteSecret(input *DeleteSecretInput) error {
conn, err := c.GetConnection(&input.ModelName)
if err != nil {
return err
}

secretAPIClient := c.getSecretAPIClient(conn)
// TODO: think about removing concrete revision.
err = secretAPIClient.RemoveSecret(nil, input.Name, nil)
if err != nil {
return err
}

return nil
}
87 changes: 87 additions & 0 deletions internal/juju/secrets_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2024 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package juju

import (
"github.com/juju/juju/api"
"testing"

"github.com/stretchr/testify/suite"
"go.uber.org/mock/gomock"
)

// Basic imports

type SecretSuite struct {
suite.Suite

testModelName string

mockSecretClient *MockSecretAPIClient
mockClient *MockClientAPIClient
mockResourceAPIClient *MockResourceAPIClient
mockConnection *MockConnection
mockModelConfigClient *MockModelConfigAPIClient
mockSharedClient *MockSharedClient
}

func (s *SecretSuite) SetupTest() {}

func (s *SecretSuite) setupMocks(t *testing.T) *gomock.Controller {
s.testModelName = "test-secret-model"

ctlr := gomock.NewController(t)
s.mockSecretClient = NewMockSecretAPIClient(ctlr)

s.mockConnection = NewMockConnection(ctlr)
s.mockConnection.EXPECT().Close().Return(nil).AnyTimes()

log := func(msg string, additionalFields ...map[string]interface{}) {
s.T().Logf("logging from shared client %q, %+v", msg, additionalFields)
}
s.mockSharedClient = NewMockSharedClient(ctlr)
s.mockSharedClient.EXPECT().Debugf(gomock.Any(), gomock.Any()).Do(log).AnyTimes()
s.mockSharedClient.EXPECT().Errorf(gomock.Any(), gomock.Any()).Do(log).AnyTimes()
s.mockSharedClient.EXPECT().Tracef(gomock.Any(), gomock.Any()).Do(log).AnyTimes()
s.mockSharedClient.EXPECT().JujuLogger().Return(&jujuLoggerShim{}).AnyTimes()
s.mockSharedClient.EXPECT().GetConnection(&s.testModelName).Return(s.mockConnection, nil).AnyTimes()

return ctlr
}

func (s *SecretSuite) getSecretsClient() secretsClient {
return secretsClient{
SharedClient: s.mockSharedClient,
getSecretAPIClient: func(connection api.Connection) SecretAPIClient {
return s.mockSecretClient
},
}
}

func (s *SecretSuite) TestCreateSecret() {
ctlr := s.setupMocks(s.T())
defer ctlr.Finish()

s.mockSecretClient.EXPECT().CreateSecret(
"test-secret", "test info", map[string]string{"key": "value"},
).Return("secret-id", nil).AnyTimes()

client := s.getSecretsClient()
output, err := client.CreateSecret(&CreateSecretInput{
ModelName: s.testModelName,
Name: "test-secret",
Value: map[string]string{"key": "value"},
Info: "test info",
})
s.Require().NoError(err)
s.Require().NotNil(output)
s.Require().Equal("secret-id", output.SecretId)

}

// In order for 'go test' to run this suite, we need to create
// a normal test function and pass our suite to suite.Run
func TestUserSecretSuite(t *testing.T) {
suite.Run(t, new(SecretSuite))
}
Loading

0 comments on commit eb0e7f4

Please sign in to comment.