Define scheme for connection secret metadata

Signed-off-by: Hasan Turken <turkenh@gmail.com>
This commit is contained in:
Hasan Turken 2022-03-01 12:35:47 +03:00
parent 15cf494997
commit 06c155d60b
No known key found for this signature in database
GPG Key ID: D7AA042F8F8B488E
6 changed files with 79 additions and 49 deletions

View File

@ -16,19 +16,17 @@ limitations under the License.
package v1
import "k8s.io/apimachinery/pkg/runtime"
import (
corev1 "k8s.io/api/core/v1"
)
// PublishConnectionDetailsTo represents configuration of a connection secret.
type PublishConnectionDetailsTo struct {
// Name is the name of the connection secret
// Name is the name of the connection secret.
Name string `json:"name"`
// Metadata is secret store specific key/value pairs to be used as metadata
// Please note, expected keys will differ for each store type. For example,
// it could be "labels" and "annotations" in case of "Kubernetes", but it
// would be "tags" for "AWS Secret Manager".
// +kubebuilder:pruning:PreserveUnknownFields
Metadata runtime.RawExtension `json:"metadata,omitempty"`
// Metadata is the metadata for connection secret.
Metadata ConnectionSecretMetadata `json:"metadata,omitempty"`
// SecretStoreConfigRef specifies which secret store config should be used
// for this ConnectionSecret.
@ -37,6 +35,21 @@ type PublishConnectionDetailsTo struct {
SecretStoreConfigRef *Reference `json:"configRef,omitempty"`
}
// ConnectionSecretMetadata represents metadata of a connection secret.
type ConnectionSecretMetadata struct {
// Labels are the labels/tags to be added to connection secret.
// - For Kubernetes secrets, this will be used as "metadata.labels".
// - It is up to Secret Store implementation for others store types.
Labels map[string]string `json:"labels"`
// Annotations are the annotations to be added to connection secret.
// - For Kubernetes secrets, this will be used as "metadata.annotations".
// - It is up to Secret Store implementation for others store types.
Annotations map[string]string `json:"annotations"`
// Type is the SecretType for the connection secret.
// - Only valid for Kubernetes Secret Stores.
Type corev1.SecretType `json:"type"`
}
// SecretStoreType represents a secret store type.
type SecretStoreType string

View File

@ -93,6 +93,35 @@ func (in *ConditionedStatus) DeepCopy() *ConditionedStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConnectionSecretMetadata) DeepCopyInto(out *ConnectionSecretMetadata) {
*out = *in
if in.Labels != nil {
in, out := &in.Labels, &out.Labels
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.Annotations != nil {
in, out := &in.Annotations, &out.Annotations
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectionSecretMetadata.
func (in *ConnectionSecretMetadata) DeepCopy() *ConnectionSecretMetadata {
if in == nil {
return nil
}
out := new(ConnectionSecretMetadata)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *EnvSelector) DeepCopyInto(out *EnvSelector) {
*out = *in

View File

@ -106,7 +106,8 @@ func (m *DetailsManager) PublishConnection(ctx context.Context, mg resource.Mana
// UnpublishConnection deletes connection details secret from the configured
// connection Store.
// TODO(turkenh): Refactor this method once existing interface methods refactored.
// TODO(turkenh): Refactor this method once the `managed.ConnectionPublisher`
// interface methods refactored per new types: SecretOwner and KeyValues
func (m *DetailsManager) UnpublishConnection(ctx context.Context, mg resource.Managed, c managed.ConnectionDetails) error {
return m.unpublishConnection(ctx, mg.(SecretOwner), store.KeyValues(c))
}
@ -135,7 +136,7 @@ func (m *DetailsManager) publishConnection(ctx context.Context, so SecretOwner,
return errors.Wrap(ss.WriteKeyValues(ctx, store.Secret{
Name: p.Name,
Scope: so.GetNamespace(),
Metadata: p.Metadata.Raw,
Metadata: p.Metadata,
}, kv), errWriteStore)
}
@ -154,6 +155,6 @@ func (m *DetailsManager) unpublishConnection(ctx context.Context, so SecretOwner
return errors.Wrap(ss.DeleteKeyValues(ctx, store.Secret{
Name: p.Name,
Scope: so.GetNamespace(),
Metadata: p.Metadata.Raw,
Metadata: p.Metadata,
}, kv), errDeleteFromStore)
}

View File

@ -18,7 +18,6 @@ package kubernetes
import (
"context"
"encoding/json"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
@ -35,23 +34,16 @@ import (
// Error strings.
const (
errGetSecret = "cannot get secret"
errDeleteSecret = "cannot delete secret"
errUpdateSecret = "cannot update secret"
errApplySecret = "cannot apply secret"
errParseMetadata = "cannot parse metadata"
errGetSecret = "cannot get secret"
errDeleteSecret = "cannot delete secret"
errUpdateSecret = "cannot update secret"
errApplySecret = "cannot apply secret"
errExtractKubernetesAuthCreds = "cannot extract kubernetes auth credentials"
errBuildRestConfig = "cannot build rest config kubeconfig"
errBuildClient = "cannot build Kubernetes client"
)
type secretMetadata struct {
Labels map[string]string `json:"labels"`
Annotations map[string]string `json:"annotations"`
Type corev1.SecretType `json:"type"`
}
// SecretStore is a Kubernetes Secret Store.
type SecretStore struct {
client resource.ClientApplicator
@ -101,24 +93,17 @@ func (ss *SecretStore) ReadKeyValues(ctx context.Context, i store.Secret) (store
// WriteKeyValues writes key value pairs to a given Kubernetes Secret.
func (ss *SecretStore) WriteKeyValues(ctx context.Context, i store.Secret, kv store.KeyValues) error {
meta := secretMetadata{}
if len(i.Metadata) > 0 {
if err := json.Unmarshal(i.Metadata, &meta); err != nil {
return errors.Wrap(err, errParseMetadata)
}
}
t := resource.SecretTypeConnection
if meta.Type != "" {
t = meta.Type
if i.Metadata.Type != "" {
t = i.Metadata.Type
}
s := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: i.Name,
Namespace: ss.namespaceForSecret(i),
Labels: meta.Labels,
Annotations: meta.Annotations,
Labels: i.Metadata.Labels,
Annotations: i.Metadata.Annotations,
},
Type: t,
Data: kv,

View File

@ -151,17 +151,6 @@ func TestSecretStoreWriteKeyValues(t *testing.T) {
args
want
}{
"CannotParseMetadata": {
reason: "Should return a proper error when metadata cannot be parsed.",
args: args{
secret: store.Secret{
Metadata: []byte("malformed-json"),
},
},
want: want{
err: errors.Wrap(errors.New("invalid character 'm' looking for beginning of value"), errParseMetadata),
},
},
"ApplyFailed": {
reason: "Should return a proper error when cannot apply.",
args: args{
@ -288,9 +277,18 @@ func TestSecretStoreWriteKeyValues(t *testing.T) {
}),
},
secret: store.Secret{
Name: fakeSecretName,
Scope: fakeSecretNamespace,
Metadata: []byte(`{ "labels":{ "environment": "unit-test","reason": "testing"},"annotations":{"some-annotation-key": "some-annotation-value"},"type": "Opaque"}`),
Name: fakeSecretName,
Scope: fakeSecretNamespace,
Metadata: v1.ConnectionSecretMetadata{
Labels: map[string]string{
"environment": "unit-test",
"reason": "testing",
},
Annotations: map[string]string{
"some-annotation-key": "some-annotation-value",
},
Type: "Opaque",
},
},
kv: store.KeyValues(fakeKV),
},

View File

@ -16,6 +16,10 @@
package store
import (
v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
)
// KeyValues is a map with sensitive values.
type KeyValues map[string][]byte
@ -23,5 +27,5 @@ type KeyValues map[string][]byte
type Secret struct {
Name string
Scope string
Metadata []byte
Metadata v1.ConnectionSecretMetadata
}