mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-15 17:51:01 +00:00
Implement Doppler Secret Push and Delete functions (#3200)
* Implement Doppler Secret Push and Delete functions Signed-off-by: Carter Cook <carter.cook@filedeploy.com> * Better error formatting (PR review #3200) Signed-off-by: Carter Cook <carter.cook@filedeploy.com> --------- Signed-off-by: Carter Cook <carter.cook@filedeploy.com>
This commit is contained in:
parent
6edd8d38dd
commit
1fbd7a01e1
4 changed files with 240 additions and 12 deletions
|
@ -35,8 +35,10 @@ import (
|
|||
const (
|
||||
customBaseURLEnvVar = "DOPPLER_BASE_URL"
|
||||
verifyTLSOverrideEnvVar = "DOPPLER_VERIFY_TLS"
|
||||
errGetSecret = "could not get secret %s: %s"
|
||||
errGetSecrets = "could not get secrets %s"
|
||||
errGetSecret = "could not get secret %s: %w"
|
||||
errGetSecrets = "could not get secrets %w"
|
||||
errDeleteSecrets = "could not delete secrets %s: %w"
|
||||
errPushSecrets = "could not push secrets %s: %w"
|
||||
errUnmarshalSecretMap = "unable to unmarshal secret %s: %w"
|
||||
secretsDownloadFileKey = "DOPPLER_SECRETS_FILE"
|
||||
errDopplerTokenSecretName = "missing auth.secretRef.dopplerToken.name"
|
||||
|
@ -63,6 +65,7 @@ type SecretsClientInterface interface {
|
|||
Authenticate() error
|
||||
GetSecret(request dClient.SecretRequest) (*dClient.SecretResponse, error)
|
||||
GetSecrets(request dClient.SecretsRequest) (*dClient.SecretsResponse, error)
|
||||
UpdateSecrets(request dClient.UpdateSecretsRequest) error
|
||||
}
|
||||
|
||||
func (c *Client) setAuth(ctx context.Context) error {
|
||||
|
@ -94,12 +97,44 @@ func (c *Client) Validate() (esv1beta1.ValidationResult, error) {
|
|||
return esv1beta1.ValidationResultReady, nil
|
||||
}
|
||||
|
||||
func (c *Client) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
func (c *Client) DeleteSecret(_ context.Context, ref esv1beta1.PushSecretRemoteRef) error {
|
||||
request := dClient.UpdateSecretsRequest{
|
||||
ChangeRequests: []dClient.Change{
|
||||
{
|
||||
Name: ref.GetRemoteKey(),
|
||||
OriginalName: ref.GetRemoteKey(),
|
||||
ShouldDelete: true,
|
||||
},
|
||||
},
|
||||
Project: c.project,
|
||||
Config: c.config,
|
||||
}
|
||||
|
||||
err := c.doppler.UpdateSecrets(request)
|
||||
if err != nil {
|
||||
return fmt.Errorf(errDeleteSecrets, ref.GetRemoteKey(), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.PushSecretData) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
func (c *Client) PushSecret(_ context.Context, secret *corev1.Secret, data esv1beta1.PushSecretData) error {
|
||||
value := secret.Data[data.GetSecretKey()]
|
||||
|
||||
request := dClient.UpdateSecretsRequest{
|
||||
Secrets: dClient.Secrets{
|
||||
data.GetRemoteKey(): string(value),
|
||||
},
|
||||
Project: c.project,
|
||||
Config: c.config,
|
||||
}
|
||||
|
||||
err := c.doppler.UpdateSecrets(request)
|
||||
if err != nil {
|
||||
return fmt.Errorf(errPushSecrets, data.GetRemoteKey(), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) GetSecret(_ context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
|
||||
|
|
|
@ -41,7 +41,12 @@ type httpRequestBody []byte
|
|||
|
||||
type Secrets map[string]string
|
||||
|
||||
type RawSecrets map[string]*interface{}
|
||||
type Change struct {
|
||||
Name string `json:"name"`
|
||||
OriginalName string `json:"originalName"`
|
||||
Value *string `json:"value"`
|
||||
ShouldDelete bool `json:"shouldDelete,omitempty"`
|
||||
}
|
||||
|
||||
type APIError struct {
|
||||
Err error
|
||||
|
@ -74,9 +79,10 @@ type SecretsRequest struct {
|
|||
}
|
||||
|
||||
type UpdateSecretsRequest struct {
|
||||
Secrets RawSecrets `json:"secrets,omitempty"`
|
||||
Project string `json:"project,omitempty"`
|
||||
Config string `json:"config,omitempty"`
|
||||
Secrets Secrets `json:"secrets,omitempty"`
|
||||
ChangeRequests []Change `json:"change_requests,omitempty"`
|
||||
Project string `json:"project,omitempty"`
|
||||
Config string `json:"config,omitempty"`
|
||||
}
|
||||
|
||||
type secretResponseBody struct {
|
||||
|
|
|
@ -21,7 +21,9 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/doppler/client"
|
||||
|
@ -31,11 +33,15 @@ import (
|
|||
const (
|
||||
validSecretName = "API_KEY"
|
||||
validSecretValue = "3a3ea4f5"
|
||||
validRemoteKey = "REMOTE_KEY"
|
||||
dopplerProject = "DOPPLER_PROJECT"
|
||||
dopplerProjectVal = "auth-api"
|
||||
missingSecret = "INVALID_NAME"
|
||||
invalidSecret = "doppler_project"
|
||||
invalidRemoteKey = "INVALID_REMOTE_KEY"
|
||||
missingSecretErr = "could not get secret"
|
||||
missingDeleteErr = "could not delete secrets"
|
||||
missingPushErr = "could not push secrets"
|
||||
)
|
||||
|
||||
type dopplerTestCase struct {
|
||||
|
@ -50,12 +56,43 @@ type dopplerTestCase struct {
|
|||
expectedData map[string][]byte
|
||||
}
|
||||
|
||||
type updateSecretCase struct {
|
||||
label string
|
||||
fakeClient *fake.DopplerClient
|
||||
request client.UpdateSecretsRequest
|
||||
remoteRef *esv1alpha1.PushSecretRemoteRef
|
||||
secret corev1.Secret
|
||||
secretData esv1beta1.PushSecretData
|
||||
apiErr error
|
||||
expectError string
|
||||
}
|
||||
|
||||
func makeValidAPIRequest() client.SecretRequest {
|
||||
return client.SecretRequest{
|
||||
Name: validSecretName,
|
||||
}
|
||||
}
|
||||
|
||||
func makeValidPushRequest() client.UpdateSecretsRequest {
|
||||
return client.UpdateSecretsRequest{
|
||||
Secrets: client.Secrets{
|
||||
validRemoteKey: validSecretValue,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeValidDeleteRequest() client.UpdateSecretsRequest {
|
||||
return client.UpdateSecretsRequest{
|
||||
ChangeRequests: []client.Change{
|
||||
{
|
||||
Name: validRemoteKey,
|
||||
OriginalName: validRemoteKey,
|
||||
ShouldDelete: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeValidAPIOutput() *client.SecretResponse {
|
||||
return &client.SecretResponse{
|
||||
Name: validSecretName,
|
||||
|
@ -69,6 +106,33 @@ func makeValidRemoteRef() *esv1beta1.ExternalSecretDataRemoteRef {
|
|||
}
|
||||
}
|
||||
|
||||
func makeValidPushRemoteRef() *esv1alpha1.PushSecretRemoteRef {
|
||||
return &esv1alpha1.PushSecretRemoteRef{
|
||||
RemoteKey: validRemoteKey,
|
||||
}
|
||||
}
|
||||
|
||||
func makeValidSecret() corev1.Secret {
|
||||
return corev1.Secret{
|
||||
Data: map[string][]byte{
|
||||
validSecretName: []byte(validSecretValue),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeValidSecretData() esv1alpha1.PushSecretData {
|
||||
return makeSecretData(validSecretName, *makeValidPushRemoteRef())
|
||||
}
|
||||
|
||||
func makeSecretData(key string, ref esv1alpha1.PushSecretRemoteRef) esv1alpha1.PushSecretData {
|
||||
return esv1alpha1.PushSecretData{
|
||||
Match: esv1alpha1.PushSecretMatch{
|
||||
SecretKey: key,
|
||||
RemoteRef: ref,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeValidDopplerTestCase() *dopplerTestCase {
|
||||
return &dopplerTestCase{
|
||||
fakeClient: &fake.DopplerClient{},
|
||||
|
@ -82,6 +146,17 @@ func makeValidDopplerTestCase() *dopplerTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
func makeValidUpdateSecretTestCase() *updateSecretCase {
|
||||
return &updateSecretCase{
|
||||
fakeClient: &fake.DopplerClient{},
|
||||
remoteRef: makeValidPushRemoteRef(),
|
||||
secret: makeValidSecret(),
|
||||
secretData: makeValidSecretData(),
|
||||
apiErr: nil,
|
||||
expectError: "",
|
||||
}
|
||||
}
|
||||
|
||||
func makeValidDopplerTestCaseCustom(tweaks ...func(pstc *dopplerTestCase)) *dopplerTestCase {
|
||||
pstc := makeValidDopplerTestCase()
|
||||
for _, fn := range tweaks {
|
||||
|
@ -91,6 +166,15 @@ func makeValidDopplerTestCaseCustom(tweaks ...func(pstc *dopplerTestCase)) *dopp
|
|||
return pstc
|
||||
}
|
||||
|
||||
func makeValidUpdateSecretCaseCustom(tweaks ...func(pstc *updateSecretCase)) *updateSecretCase {
|
||||
pstc := makeValidUpdateSecretTestCase()
|
||||
for _, fn := range tweaks {
|
||||
fn(pstc)
|
||||
}
|
||||
pstc.fakeClient.WithUpdateValue(pstc.request, pstc.apiErr)
|
||||
return pstc
|
||||
}
|
||||
|
||||
func TestGetSecret(t *testing.T) {
|
||||
setSecret := func(pstc *dopplerTestCase) {
|
||||
pstc.label = "set secret"
|
||||
|
@ -120,7 +204,7 @@ func TestGetSecret(t *testing.T) {
|
|||
}
|
||||
|
||||
setClientError := func(pstc *dopplerTestCase) {
|
||||
pstc.label = "invalid client error"
|
||||
pstc.label = "invalid client error" //nolint:goconst
|
||||
pstc.response = &client.SecretResponse{}
|
||||
pstc.expectError = missingSecretErr
|
||||
pstc.apiErr = fmt.Errorf("")
|
||||
|
@ -205,6 +289,93 @@ func ErrorContains(out error, want string) bool {
|
|||
return strings.Contains(out.Error(), want)
|
||||
}
|
||||
|
||||
func TestDeleteSecret(t *testing.T) {
|
||||
deleteSecret := func(pstc *updateSecretCase) {
|
||||
pstc.label = "delete secret"
|
||||
pstc.request = makeValidDeleteRequest()
|
||||
}
|
||||
|
||||
deleteMissingSecret := func(pstc *updateSecretCase) {
|
||||
pstc.label = "delete missing secret"
|
||||
pstc.request = makeValidDeleteRequest()
|
||||
pstc.remoteRef.RemoteKey = invalidRemoteKey
|
||||
pstc.expectError = missingDeleteErr
|
||||
pstc.apiErr = fmt.Errorf("")
|
||||
}
|
||||
|
||||
setClientError := func(pstc *updateSecretCase) {
|
||||
pstc.label = "invalid client error"
|
||||
pstc.request = makeValidDeleteRequest()
|
||||
pstc.expectError = missingDeleteErr
|
||||
pstc.apiErr = fmt.Errorf("")
|
||||
}
|
||||
|
||||
testCases := []*updateSecretCase{
|
||||
makeValidUpdateSecretCaseCustom(deleteSecret),
|
||||
makeValidUpdateSecretCaseCustom(deleteMissingSecret),
|
||||
makeValidUpdateSecretCaseCustom(setClientError),
|
||||
}
|
||||
|
||||
c := Client{}
|
||||
for k, tc := range testCases {
|
||||
c.doppler = tc.fakeClient
|
||||
err := c.DeleteSecret(context.Background(), tc.remoteRef)
|
||||
|
||||
if !ErrorContains(err, tc.expectError) {
|
||||
t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), tc.expectError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushSecret(t *testing.T) {
|
||||
pushSecret := func(pstc *updateSecretCase) {
|
||||
pstc.label = "push secret"
|
||||
pstc.request = makeValidPushRequest()
|
||||
}
|
||||
|
||||
pushMissingSecretKey := func(pstc *updateSecretCase) {
|
||||
pstc.label = "push missing secret key"
|
||||
pstc.secretData = makeSecretData(invalidSecret, *makeValidPushRemoteRef())
|
||||
pstc.expectError = missingPushErr
|
||||
pstc.apiErr = fmt.Errorf("")
|
||||
}
|
||||
|
||||
pushMissingRemoteSecret := func(pstc *updateSecretCase) {
|
||||
pstc.label = "push missing remote secret"
|
||||
pstc.secretData = makeSecretData(
|
||||
validSecretName,
|
||||
esv1alpha1.PushSecretRemoteRef{
|
||||
RemoteKey: invalidRemoteKey,
|
||||
},
|
||||
)
|
||||
pstc.expectError = missingPushErr
|
||||
pstc.apiErr = fmt.Errorf("")
|
||||
}
|
||||
|
||||
setClientError := func(pstc *updateSecretCase) {
|
||||
pstc.label = "invalid client error"
|
||||
pstc.expectError = missingPushErr
|
||||
pstc.apiErr = fmt.Errorf("")
|
||||
}
|
||||
|
||||
testCases := []*updateSecretCase{
|
||||
makeValidUpdateSecretCaseCustom(pushSecret),
|
||||
makeValidUpdateSecretCaseCustom(pushMissingSecretKey),
|
||||
makeValidUpdateSecretCaseCustom(pushMissingRemoteSecret),
|
||||
makeValidUpdateSecretCaseCustom(setClientError),
|
||||
}
|
||||
|
||||
c := Client{}
|
||||
for k, tc := range testCases {
|
||||
c.doppler = tc.fakeClient
|
||||
err := c.PushSecret(context.Background(), &tc.secret, tc.secretData)
|
||||
|
||||
if !ErrorContains(err, tc.expectError) {
|
||||
t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), tc.expectError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type storeModifier func(*esv1beta1.SecretStore) *esv1beta1.SecretStore
|
||||
|
||||
func makeSecretStore(fn ...storeModifier) *esv1beta1.SecretStore {
|
||||
|
|
|
@ -24,7 +24,8 @@ import (
|
|||
)
|
||||
|
||||
type DopplerClient struct {
|
||||
getSecret func(request client.SecretRequest) (*client.SecretResponse, error)
|
||||
getSecret func(request client.SecretRequest) (*client.SecretResponse, error)
|
||||
updateSecrets func(request client.UpdateSecretsRequest) error
|
||||
}
|
||||
|
||||
func (dc *DopplerClient) BaseURL() *url.URL {
|
||||
|
@ -44,6 +45,10 @@ func (dc *DopplerClient) GetSecrets(_ client.SecretsRequest) (*client.SecretsRes
|
|||
return &client.SecretsResponse{}, nil
|
||||
}
|
||||
|
||||
func (dc *DopplerClient) UpdateSecrets(request client.UpdateSecretsRequest) error {
|
||||
return dc.updateSecrets(request)
|
||||
}
|
||||
|
||||
func (dc *DopplerClient) WithValue(request client.SecretRequest, response *client.SecretResponse, err error) {
|
||||
if dc != nil {
|
||||
dc.getSecret = func(requestIn client.SecretRequest) (*client.SecretResponse, error) {
|
||||
|
@ -54,3 +59,14 @@ func (dc *DopplerClient) WithValue(request client.SecretRequest, response *clien
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (dc *DopplerClient) WithUpdateValue(request client.UpdateSecretsRequest, err error) {
|
||||
if dc != nil {
|
||||
dc.updateSecrets = func(requestIn client.UpdateSecretsRequest) error {
|
||||
if !cmp.Equal(requestIn, request) {
|
||||
return fmt.Errorf("unexpected test argument")
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue