mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
fix: ensure existing labels are retained for secrets in GCP secrets m… (#4160)
* fix: ensure existing labels are retained for secrets in GCP secrets manager for existing secrets (#4016) Signed-off-by: Craig Newton <newtondev@gmail.com> * fix: ensure existing labels are retained for secrets in GCP secrets manager for existing secrets (#4016) Signed-off-by: Craig Newton <newtondev@gmail.com> * fix: add missing header to push_secret_test.go Signed-off-by: Craig Newton <newtondev@gmail.com> --------- Signed-off-by: Craig Newton <newtondev@gmail.com>
This commit is contained in:
parent
34f526f134
commit
388158a4d4
3 changed files with 166 additions and 5 deletions
|
@ -111,3 +111,44 @@ The operator will fetch the GCP Secret Manager secret and inject it as a `Kind=S
|
|||
```
|
||||
kubectl get secret secret-to-be-created -n <namespace> -o jsonpath='{.data.dev-secret-test}' | base64 -d
|
||||
```
|
||||
|
||||
### PushSecret owning an existing Google Secret Manager Secret
|
||||
|
||||
There are some use cases where you want to use PushSecret for an existing Google Secret Manager Secret that already has labels defined. For example when the creation of the secret is managed by another controller like Kubernetes Config Connector (KCC) and the updating of the secret is managed by ESO.
|
||||
|
||||
To allow ESO to take ownership of the existing Google Secret Manager Secret, you need to add the label `"managed-by": "external-secrets"`.
|
||||
|
||||
By default, the PushSecret spec will replace any existing labels on the existing GCP Secret Manager Secret. To prevent this, a new field was added to the `spec.data.metadata` object called `mergePolicy` which defaults to `Replace` to ensure that there are no breaking changes and is backward compatible. The other option for this field is `Merge` which will merge the existing labels on the Google Secret Manager Secret with the labels defined in the PushSecret spec. This ensures that the existing labels defined on the Google Secret Manager Secret are retained.
|
||||
|
||||
Example of using the `mergePolicy` field:
|
||||
|
||||
```yaml
|
||||
apiVersion: external-secrets.io/v1alpha1
|
||||
kind: PushSecret
|
||||
metadata:
|
||||
name: pushsecret-example
|
||||
namespace: default
|
||||
spec:
|
||||
updatePolicy: Replace
|
||||
deletionPolicy: None
|
||||
refreshInterval: 1h
|
||||
secretStoreRefs:
|
||||
- name: gcp-secretstore
|
||||
kind: SecretStore
|
||||
selector:
|
||||
secret:
|
||||
name: bestpokemon
|
||||
template:
|
||||
data:
|
||||
bestpokemon: "{{ .bestpokemon }}"
|
||||
data:
|
||||
- conversionStrategy: None
|
||||
metadata:
|
||||
mergePolicy: Merge
|
||||
labels:
|
||||
anotherLabel: anotherValue
|
||||
match:
|
||||
secretKey: bestpokemon
|
||||
remoteRef:
|
||||
remoteKey: best-pokemon
|
||||
```
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
|
||||
"cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
|
||||
"github.com/tidwall/sjson"
|
||||
|
@ -26,10 +27,18 @@ import (
|
|||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
)
|
||||
|
||||
type PushSecretMetadataMergePolicy string
|
||||
|
||||
const (
|
||||
PushSecretMetadataMergePolicyReplace PushSecretMetadataMergePolicy = "Replace"
|
||||
PushSecretMetadataMergePolicyMerge PushSecretMetadataMergePolicy = "Merge"
|
||||
)
|
||||
|
||||
type Metadata struct {
|
||||
Annotations map[string]string `json:"annotations"`
|
||||
Labels map[string]string `json:"labels"`
|
||||
Topics []string `json:"topics,omitempty"`
|
||||
Annotations map[string]string `json:"annotations"`
|
||||
Labels map[string]string `json:"labels"`
|
||||
Topics []string `json:"topics,omitempty"`
|
||||
MergePolicy PushSecretMetadataMergePolicy `json:"mergePolicy,omitempty"`
|
||||
}
|
||||
|
||||
func newPushSecretBuilder(payload []byte, data esv1beta1.PushSecretData) (pushSecretBuilder, error) {
|
||||
|
@ -75,11 +84,18 @@ func (b *psBuilder) buildMetadata(_, labels map[string]string, _ []*secretmanage
|
|||
if err := decoder.Decode(&metadata); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("failed to decode PushSecret metadata: %w", err)
|
||||
}
|
||||
|
||||
if metadata.MergePolicy == "" {
|
||||
// Set default MergePolicy to be Replace
|
||||
metadata.MergePolicy = PushSecretMetadataMergePolicyReplace
|
||||
}
|
||||
}
|
||||
|
||||
newLabels := map[string]string{}
|
||||
if metadata.Labels != nil {
|
||||
newLabels = metadata.Labels
|
||||
maps.Copy(newLabels, metadata.Labels)
|
||||
if metadata.MergePolicy == PushSecretMetadataMergePolicyMerge {
|
||||
// Keep labels from the existing GCP Secret Manager Secret
|
||||
maps.Copy(newLabels, labels)
|
||||
}
|
||||
newLabels[managedByKey] = managedByValue
|
||||
|
||||
|
|
104
pkg/provider/gcp/secretmanager/push_secret_test.go
Normal file
104
pkg/provider/gcp/secretmanager/push_secret_test.go
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package secretmanager
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
|
||||
testingfake "github.com/external-secrets/external-secrets/pkg/provider/testing/fake"
|
||||
)
|
||||
|
||||
func TestBuildMetadata(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
labels map[string]string
|
||||
metadata *apiextensionsv1.JSON
|
||||
expectedError bool
|
||||
expectedLabels map[string]string
|
||||
expectedAnnotations map[string]string
|
||||
expectedTopics []string
|
||||
}{
|
||||
{
|
||||
name: "secret not managed by external secrets",
|
||||
labels: map[string]string{
|
||||
"someKey": "someValue",
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "metadata with default MergePolicy of Replace",
|
||||
labels: map[string]string{
|
||||
managedByKey: managedByValue,
|
||||
"someOtherKey": "someOtherValue",
|
||||
},
|
||||
metadata: &apiextensionsv1.JSON{
|
||||
Raw: []byte(`{"annotations":{"key1":"value1"},"labels":{"key2":"value2"}}`),
|
||||
},
|
||||
expectedError: false,
|
||||
expectedLabels: map[string]string{
|
||||
managedByKey: managedByValue,
|
||||
"key2": "value2",
|
||||
},
|
||||
expectedAnnotations: map[string]string{
|
||||
"key1": "value1",
|
||||
},
|
||||
expectedTopics: nil,
|
||||
},
|
||||
{
|
||||
name: "metadata with merge policy",
|
||||
labels: map[string]string{
|
||||
managedByKey: managedByValue,
|
||||
"existingKey": "existingValue",
|
||||
},
|
||||
metadata: &apiextensionsv1.JSON{
|
||||
Raw: []byte(`{"annotations":{"key1":"value1"},"labels":{"key2":"value2"},"mergePolicy":"Merge"}`),
|
||||
},
|
||||
expectedError: false,
|
||||
expectedLabels: map[string]string{
|
||||
managedByKey: managedByValue,
|
||||
"existingKey": "existingValue",
|
||||
"key2": "value2",
|
||||
},
|
||||
expectedAnnotations: map[string]string{
|
||||
"key1": "value1",
|
||||
},
|
||||
expectedTopics: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
psData := testingfake.PushSecretData{
|
||||
Metadata: tt.metadata,
|
||||
}
|
||||
builder := &psBuilder{
|
||||
pushSecretData: psData,
|
||||
}
|
||||
|
||||
annotations, labels, topics, err := builder.buildMetadata(nil, tt.labels, nil)
|
||||
if tt.expectedError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedLabels, labels)
|
||||
assert.Equal(t, tt.expectedAnnotations, annotations)
|
||||
assert.Equal(t, tt.expectedTopics, topics)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue