diff --git a/internal/juju/client.go b/internal/juju/client.go index 8e48e3cb..3918affd 100644 --- a/internal/juju/client.go +++ b/internal/juju/client.go @@ -44,6 +44,7 @@ type Client struct { Offers offersClient SSHKeys sshKeysClient Users usersClient + Secrets secretsClient } type jujuModel struct { @@ -87,6 +88,7 @@ func NewClient(ctx context.Context, config ControllerConfiguration) (*Client, er Offers: *newOffersClient(sc), SSHKeys: *newSSHKeysClient(sc), Users: *newUsersClient(sc), + Secrets: *newSecretsClient(sc), }, nil } diff --git a/internal/juju/common_test.go b/internal/juju/common_test.go new file mode 100644 index 00000000..a02b54a4 --- /dev/null +++ b/internal/juju/common_test.go @@ -0,0 +1,41 @@ +// Copyright 2024 Canonical Ltd. +// Licensed under the Apache License, Version 2.0, see LICENCE file for details. + +package juju + +import ( + "testing" + + "github.com/stretchr/testify/suite" + "go.uber.org/mock/gomock" +) + +type JujuSuite struct { + suite.Suite + + testModelName string + + mockConnection *MockConnection + mockSharedClient *MockSharedClient +} + +func (s *JujuSuite) setupMocks(t *testing.T) *gomock.Controller { + s.testModelName = "test-secret-model" + + ctlr := gomock.NewController(t) + + 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 +} diff --git a/internal/juju/interfaces.go b/internal/juju/interfaces.go index 85af3c64..689336d5 100644 --- a/internal/juju/interfaces.go +++ b/internal/juju/interfaces.go @@ -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" ) @@ -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 +} diff --git a/internal/juju/mock_test.go b/internal/juju/mock_test.go index d12e790c..c169cc85 100644 --- a/internal/juju/mock_test.go +++ b/internal/juju/mock_test.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/juju/terraform-provider-juju/internal/juju (interfaces: SharedClient,ClientAPIClient,ApplicationAPIClient,ModelConfigAPIClient,ResourceAPIClient) +// Source: github.com/juju/terraform-provider-juju/internal/juju (interfaces: SharedClient,ClientAPIClient,ApplicationAPIClient,ModelConfigAPIClient,ResourceAPIClient,SecretAPIClient) // // Generated by this command: // -// mockgen -package juju -destination mock_test.go github.com/juju/terraform-provider-juju/internal/juju SharedClient,ClientAPIClient,ApplicationAPIClient,ModelConfigAPIClient,ResourceAPIClient +// mockgen -package juju -destination mock_test.go github.com/juju/terraform-provider-juju/internal/juju SharedClient,ClientAPIClient,ApplicationAPIClient,ModelConfigAPIClient,ResourceAPIClient,SecretAPIClient // // Package juju is a generated GoMock package. @@ -17,10 +17,12 @@ import ( application "github.com/juju/juju/api/client/application" client "github.com/juju/juju/api/client/client" resources "github.com/juju/juju/api/client/resources" + secrets "github.com/juju/juju/api/client/secrets" charm0 "github.com/juju/juju/api/common/charm" constraints "github.com/juju/juju/core/constraints" model "github.com/juju/juju/core/model" resources0 "github.com/juju/juju/core/resources" + secrets0 "github.com/juju/juju/core/secrets" params "github.com/juju/juju/rpc/params" names "github.com/juju/names/v4" gomock "go.uber.org/mock/gomock" @@ -569,3 +571,84 @@ func (mr *MockResourceAPIClientMockRecorder) ListResources(arg0 any) *gomock.Cal mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListResources", reflect.TypeOf((*MockResourceAPIClient)(nil).ListResources), arg0) } + +// MockSecretAPIClient is a mock of SecretAPIClient interface. +type MockSecretAPIClient struct { + ctrl *gomock.Controller + recorder *MockSecretAPIClientMockRecorder +} + +// MockSecretAPIClientMockRecorder is the mock recorder for MockSecretAPIClient. +type MockSecretAPIClientMockRecorder struct { + mock *MockSecretAPIClient +} + +// NewMockSecretAPIClient creates a new mock instance. +func NewMockSecretAPIClient(ctrl *gomock.Controller) *MockSecretAPIClient { + mock := &MockSecretAPIClient{ctrl: ctrl} + mock.recorder = &MockSecretAPIClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSecretAPIClient) EXPECT() *MockSecretAPIClientMockRecorder { + return m.recorder +} + +// CreateSecret mocks base method. +func (m *MockSecretAPIClient) CreateSecret(arg0, arg1 string, arg2 map[string]string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateSecret", arg0, arg1, arg2) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateSecret indicates an expected call of CreateSecret. +func (mr *MockSecretAPIClientMockRecorder) CreateSecret(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSecret", reflect.TypeOf((*MockSecretAPIClient)(nil).CreateSecret), arg0, arg1, arg2) +} + +// ListSecrets mocks base method. +func (m *MockSecretAPIClient) ListSecrets(arg0 bool, arg1 secrets0.Filter) ([]secrets.SecretDetails, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListSecrets", arg0, arg1) + ret0, _ := ret[0].([]secrets.SecretDetails) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListSecrets indicates an expected call of ListSecrets. +func (mr *MockSecretAPIClientMockRecorder) ListSecrets(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListSecrets", reflect.TypeOf((*MockSecretAPIClient)(nil).ListSecrets), arg0, arg1) +} + +// RemoveSecret mocks base method. +func (m *MockSecretAPIClient) RemoveSecret(arg0 *secrets0.URI, arg1 string, arg2 *int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemoveSecret", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// RemoveSecret indicates an expected call of RemoveSecret. +func (mr *MockSecretAPIClientMockRecorder) RemoveSecret(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveSecret", reflect.TypeOf((*MockSecretAPIClient)(nil).RemoveSecret), arg0, arg1, arg2) +} + +// UpdateSecret mocks base method. +func (m *MockSecretAPIClient) UpdateSecret(arg0 *secrets0.URI, arg1 string, arg2 *bool, arg3, arg4 string, arg5 map[string]string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateSecret", arg0, arg1, arg2, arg3, arg4, arg5) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateSecret indicates an expected call of UpdateSecret. +func (mr *MockSecretAPIClientMockRecorder) UpdateSecret(arg0, arg1, arg2, arg3, arg4, arg5 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateSecret", reflect.TypeOf((*MockSecretAPIClient)(nil).UpdateSecret), arg0, arg1, arg2, arg3, arg4, arg5) +} diff --git a/internal/juju/package_test.go b/internal/juju/package_test.go index ebcbff12..8a3f724f 100644 --- a/internal/juju/package_test.go +++ b/internal/juju/package_test.go @@ -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 diff --git a/internal/juju/secrets.go b/internal/juju/secrets.go new file mode 100644 index 00000000..533fb13f --- /dev/null +++ b/internal/juju/secrets.go @@ -0,0 +1,240 @@ +// Copyright 2024 Canonical Ltd. +// Licensed under the Apache License, Version 2.0, see LICENCE file for details. + +package juju + +import ( + "encoding/base64" + "errors" + "fmt" + + jujuerrors "github.com/juju/errors" + "github.com/juju/juju/api" + apisecrets "github.com/juju/juju/api/client/secrets" + coresecrets "github.com/juju/juju/core/secrets" +) + +var SecretNotFoundError = &secretNotFoundError{} + +type secretNotFoundError struct { + secretId string +} + +func (se *secretNotFoundError) Error() string { + if se.secretId != "" { + return fmt.Sprintf("secret %q was not found", se.secretId) + } else { + return "secret was not found" + } +} + +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 { + SecretId string + ModelName string + Name *string + Revision *int +} + +type ReadSecretOutput struct { + SecretId string + Name string + Value map[string]string + Info string +} + +type UpdateSecretInput struct { + SecretId string + ModelName string + Name *string + Value *map[string]string + AutoPrune *bool + Info *string +} + +type DeleteSecretInput struct { + SecretId string + ModelName 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 CreateSecretOutput{}, err + } + defer func() { _ = conn.Close() }() + + secretAPIClient := c.getSecretAPIClient(conn) + + // Encode the secret values as base64 + encodedValue := make(map[string]string, len(input.Value)) + for k, v := range input.Value { + encodedValue[k] = base64.StdEncoding.EncodeToString([]byte(v)) + } + + secretId, err := secretAPIClient.CreateSecret(input.Name, input.Info, encodedValue) + if err != nil { + return CreateSecretOutput{}, typedError(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 ReadSecretOutput{}, err + } + defer func() { _ = conn.Close() }() + + secretAPIClient := c.getSecretAPIClient(conn) + + var secretURI *coresecrets.URI + if input.SecretId != "" { + secretURI, err = coresecrets.ParseURI(input.SecretId) + if err != nil { + return ReadSecretOutput{}, err + } + } else { + secretURI = nil + } + secretFilter := coresecrets.Filter{ + URI: secretURI, + Label: input.Name, + Revision: input.Revision, + } + + results, err := secretAPIClient.ListSecrets(true, secretFilter) + if err != nil { + return ReadSecretOutput{}, typedError(err) + } + if len(results) < 1 { + return ReadSecretOutput{}, &secretNotFoundError{secretId: input.SecretId} + } + if results[0].Error != "" { + return ReadSecretOutput{}, errors.New(results[0].Error) + } + + // Decode the secret values from base64 + decodedValue, err := results[0].Value.Values() + if err != nil { + return ReadSecretOutput{}, err + } + + return ReadSecretOutput{ + SecretId: results[0].Metadata.URI.String(), + Name: results[0].Metadata.Label, + Value: decodedValue, + Info: results[0].Metadata.Description, + }, 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) + + // Specify by ID or Name + if input.SecretId == "" && input.Name == nil { + return errors.New("must specify either secret ID or name") + } + + // Define default values + var info string + if input.Info != nil { + info = *input.Info + } else { + info = "" + } + var value map[string]string + if input.Value != nil { + // Encode the secret values as base64 + encodedValue := make(map[string]string, len(*input.Value)) + for k, v := range *input.Value { + encodedValue[k] = base64.StdEncoding.EncodeToString([]byte(v)) + } + + value = encodedValue + } else { + value = map[string]string{} + } + + if input.SecretId != "" { + // Specify by ID + secretURI, err := coresecrets.ParseURI(input.SecretId) + if err != nil { + return err + } + if input.Name == nil { + // Update secret without changing the name + err = secretAPIClient.UpdateSecret(secretURI, "", input.AutoPrune, "", info, value) + if err != nil { + return typedError(err) + } + } else { + // Update secret with a new name + err = secretAPIClient.UpdateSecret(secretURI, "", input.AutoPrune, *input.Name, info, value) + if err != nil { + return typedError(err) + } + } + } else { + return errors.New("updating secrets by name is not supported") + } + + 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) + secretURI, err := coresecrets.ParseURI(input.SecretId) + if err != nil { + return err + } + // TODO: think about removing concrete revision. + err = secretAPIClient.RemoveSecret(secretURI, "", nil) + if !errors.Is(err, jujuerrors.NotFound) { + return typedError(err) + } + + return nil +} diff --git a/internal/juju/secrets_test.go b/internal/juju/secrets_test.go new file mode 100644 index 00000000..02815fa9 --- /dev/null +++ b/internal/juju/secrets_test.go @@ -0,0 +1,308 @@ +// Copyright 2024 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package juju + +import ( + "encoding/base64" + "errors" + "testing" + + "github.com/juju/juju/api" + apisecrets "github.com/juju/juju/api/client/secrets" + coresecrets "github.com/juju/juju/core/secrets" + "github.com/stretchr/testify/suite" + "go.uber.org/mock/gomock" +) + +type SecretSuite struct { + suite.Suite + JujuSuite + + testModelName string + + mockSecretClient *MockSecretAPIClient +} + +func (s *SecretSuite) SetupTest() {} + +func (s *SecretSuite) setupMocks(t *testing.T) *gomock.Controller { + s.testModelName = "test-secret-model" + + ctlr := s.JujuSuite.setupMocks(t) + s.mockSecretClient = NewMockSecretAPIClient(ctlr) + + return ctlr +} + +func (s *SecretSuite) getSecretsClient() secretsClient { + return secretsClient{ + SharedClient: s.JujuSuite.mockSharedClient, + getSecretAPIClient: func(connection api.Connection) SecretAPIClient { + return s.mockSecretClient + }, + } +} + +func (s *SecretSuite) TestCreateSecret() { + ctlr := s.setupMocks(s.T()) + defer ctlr.Finish() + + decodedValue := map[string]string{"key": "value"} + encodedValue := map[string]string{"key": base64.StdEncoding.EncodeToString([]byte("value"))} + + s.mockSecretClient.EXPECT().CreateSecret( + "test-secret", "test info", encodedValue, + ).Return("secret-id", nil).AnyTimes() + + client := s.getSecretsClient() + output, err := client.CreateSecret(&CreateSecretInput{ + ModelName: s.testModelName, + Name: "test-secret", + Value: decodedValue, + Info: "test info", + }) + s.Require().NoError(err) + + s.Assert().NotNil(output) + s.Assert().Equal("secret-id", output.SecretId) +} + +func (s *SecretSuite) TestCreateSecretError() { + ctlr := s.setupMocks(s.T()) + defer ctlr.Finish() + + errBoom := errors.New("boom") + + decodedValue := map[string]string{"key": "value"} + encodedValue := map[string]string{"key": base64.StdEncoding.EncodeToString([]byte("value"))} + + s.mockSecretClient.EXPECT().CreateSecret( + "test-secret", "test info", encodedValue, + ).Return("", errBoom).AnyTimes() + + client := s.getSecretsClient() + output, err := client.CreateSecret(&CreateSecretInput{ + ModelName: s.testModelName, + Name: "test-secret", + Value: decodedValue, + Info: "test info", + }) + s.Require().Error(err) + + s.Assert().Equal(output, CreateSecretOutput{}) + s.Assert().Equal(errBoom, err) +} + +func (s *SecretSuite) TestReadSecret() { + ctlr := s.setupMocks(s.T()) + defer ctlr.Finish() + + secretId := "secret:9m4e2mr0ui3e8a215n4g" + secretURI, err := coresecrets.ParseURI(secretId) + s.Require().NoError(err) + secretName := "test-secret" + secretRevision := 1 + + value := base64.StdEncoding.EncodeToString([]byte("value")) + s.mockSecretClient.EXPECT().ListSecrets( + true, coresecrets.Filter{ + URI: secretURI, + Label: &secretName, + Revision: &secretRevision, + }, + ).Return([]apisecrets.SecretDetails{ + { + Metadata: coresecrets.SecretMetadata{ + Version: 1, + }, + Revisions: []coresecrets.SecretRevisionMetadata{ + { + Revision: 1, + }, + }, + Value: coresecrets.NewSecretValue(map[string]string{"key": value}), + Error: "", + }, + }, nil).AnyTimes() + + client := s.getSecretsClient() + output, err := client.ReadSecret(&ReadSecretInput{ + SecretId: secretId, + ModelName: s.testModelName, + Name: &secretName, + Revision: &secretRevision, + }) + s.Require().NoError(err) + + s.Assert().NotNil(output) + s.Assert().Equal("value", output.Value["key"]) +} + +func (s *SecretSuite) TestReadSecretError() { + ctlr := s.setupMocks(s.T()) + defer ctlr.Finish() + + secretId := "secret:9m4e2mr0ui3e8a215n4g" + secretURI, err := coresecrets.ParseURI(secretId) + s.Require().NoError(err) + + errBoom := errors.New("boom") + s.mockSecretClient.EXPECT().ListSecrets( + true, coresecrets.Filter{ + URI: secretURI, + }, + ).Return([]apisecrets.SecretDetails{ + { + Error: errBoom.Error(), + }, + }, nil).AnyTimes() + + client := s.getSecretsClient() + output, err := client.ReadSecret(&ReadSecretInput{ + SecretId: secretId, + ModelName: s.testModelName, + }) + s.Require().Error(err) + + s.Assert().Equal(output, ReadSecretOutput{}) + s.Assert().Equal(errBoom, err) +} + +func (s *SecretSuite) TestUpdateSecretWithRenaming() { + ctlr := s.setupMocks(s.T()) + defer ctlr.Finish() + + newSecretName := "test-secret2" + secretId := "secret:9m4e2mr0ui3e8a215n4g" + secretInfo := "secret info" + autoPrune := true + + decodedValue := map[string]string{"key": "value"} + encodedValue := map[string]string{"key": base64.StdEncoding.EncodeToString([]byte("value"))} + + secretURI, err := coresecrets.ParseURI(secretId) + s.Require().NoError(err) + + s.mockSecretClient.EXPECT().UpdateSecret( + secretURI, "", &autoPrune, newSecretName, "secret info", encodedValue, + ).Return(nil).AnyTimes() + + client := s.getSecretsClient() + err = client.UpdateSecret(&UpdateSecretInput{ + SecretId: secretId, + ModelName: s.testModelName, + Name: &newSecretName, + Value: &decodedValue, + AutoPrune: &autoPrune, + Info: &secretInfo, + }) + s.Require().NoError(err) + + s.mockSecretClient.EXPECT().ListSecrets( + true, coresecrets.Filter{URI: secretURI}, + ).Return([]apisecrets.SecretDetails{ + { + Metadata: coresecrets.SecretMetadata{ + Version: 1, + }, + Revisions: []coresecrets.SecretRevisionMetadata{ + { + Revision: 1, + }, + }, + Value: coresecrets.NewSecretValue(encodedValue), + Error: "", + }, + }, nil).Times(1) + + // read secret and check if value is updated + output, err := client.ReadSecret(&ReadSecretInput{ + SecretId: secretId, + ModelName: s.testModelName, + }) + s.Require().NoError(err) + + s.Assert().NotNil(output) +} + +func (s *SecretSuite) TestUpdateSecret() { + ctlr := s.setupMocks(s.T()) + defer ctlr.Finish() + + secretId := "secret:9m4e2mr0ui3e8a215n4g" + secretInfo := "secret info" + autoPrune := true + + decodedValue := map[string]string{"key": "value"} + encodedValue := map[string]string{"key": base64.StdEncoding.EncodeToString([]byte("value"))} + + secretURI, err := coresecrets.ParseURI(secretId) + s.Require().NoError(err) + + s.mockSecretClient.EXPECT().UpdateSecret( + secretURI, "", &autoPrune, "", secretInfo, encodedValue, + ).Return(nil).AnyTimes() + + client := s.getSecretsClient() + err = client.UpdateSecret(&UpdateSecretInput{ + SecretId: secretId, + ModelName: s.testModelName, + Value: &decodedValue, + AutoPrune: &autoPrune, + Info: &secretInfo, + }) + s.Require().NoError(err) + + s.mockSecretClient.EXPECT().ListSecrets( + true, coresecrets.Filter{URI: secretURI}, + ).Return([]apisecrets.SecretDetails{ + { + Metadata: coresecrets.SecretMetadata{ + Version: 1, + Description: secretInfo, + }, + Revisions: []coresecrets.SecretRevisionMetadata{ + { + Revision: 1, + }, + }, + Value: coresecrets.NewSecretValue(encodedValue), + Error: "", + }, + }, nil).Times(1) + + // read secret and check if secret info is updated + output, err := client.ReadSecret(&ReadSecretInput{ + SecretId: secretId, + ModelName: s.testModelName, + }) + s.Require().NoError(err) + + s.Assert().NotNil(output) +} + +func (s *SecretSuite) TestDeleteSecret() { + ctlr := s.setupMocks(s.T()) + defer ctlr.Finish() + + secretId := "secret:9m4e2mr0ui3e8a215n4g" + + secretURI, err := coresecrets.ParseURI(secretId) + s.Require().NoError(err) + + s.mockSecretClient.EXPECT().RemoveSecret(secretURI, "", nil).Return(nil).AnyTimes() + + client := s.getSecretsClient() + err = client.DeleteSecret(&DeleteSecretInput{ + SecretId: secretId, + ModelName: s.testModelName, + }) + s.Assert().NoError(err) +} + +// 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)) +}