feat: support customized labels for storage like secret, configmap

close #7007, #8098

Signed-off-by: Liu Ming <hit_oak_tree@126.com>
pull/8099/head
Liu Ming 5 years ago
parent 1e98e1bfde
commit 09f8cc9487

@ -60,6 +60,10 @@ func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {
f.BoolVar(&c.PassCredentialsAll, "pass-credentials", false, "pass credentials to all domains")
}
func addStorageOptionsFlags(f *pflag.FlagSet, v *values.Options) {
f.StringArrayVarP(&v.Labels, "set-label", "l", []string{}, "specify labels for release secret/configmap on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
}
// bindOutputFlag will add the output flag to the given command and bind the
// value to the given format pointer
func bindOutputFlag(cmd *cobra.Command, varRef *output.Format) {

@ -157,6 +157,7 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal
f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent")
addValueOptionsFlags(f, valueOpts)
addChartPathOptionsFlags(f, &client.ChartPathOptions)
addStorageOptionsFlags(f, valueOpts)
err := cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
requiredArgs := 2
@ -204,6 +205,15 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
return nil, err
}
labels, err := valueOpts.ParseLabels()
if err != nil {
return nil, err
}
client.Labels = labels
if len(client.Labels) > 0 {
debug("customized labels: %+v", client.Labels)
}
// Check chart dependencies to make sure all are present in /charts
chartRequested, err := loader.Load(cp)
if err != nil {

@ -109,6 +109,8 @@ type Install struct {
PostRenderer postrender.PostRenderer
// Lock to control raceconditions when the process receives a SIGTERM
Lock sync.Mutex
// Labels customized release secret/configmap labels
Labels map[string]string
}
// ChartPathOptions captures common options used for controlling chart paths
@ -302,6 +304,10 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
return rel, nil
}
if err = i.cfg.Releases.SetLabels(i.Labels); err != nil {
return nil, err
}
if i.CreateNamespace {
ns := &v1.Namespace{
TypeMeta: metav1.TypeMeta{
@ -328,7 +334,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
}
}
// If Replace is true, we need to supercede the last release.
// If Replace is true, we need to supersede the last release.
if i.Replace {
if err := i.replaceRelease(rel); err != nil {
return nil, err

@ -117,6 +117,14 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele
return nil, nil, err
}
labels, err := r.cfg.Releases.GetLabels(name, previousVersion)
if err != nil {
return nil, nil, err
}
if err = r.cfg.Releases.SetLabels(labels); err != nil {
return nil, nil, err
}
// Store a new release object with previous release's configuration
targetRelease := &release.Release{
Name: name,

@ -181,6 +181,15 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin
return nil, nil, errPending
}
// get previous release secret labels
labels, err := u.cfg.Releases.GetLabels(name, lastRelease.Version)
if err != nil {
return nil, nil, err
}
if err = u.cfg.Releases.SetLabels(labels); err != nil {
return nil, nil, err
}
var currentRelease *release.Release
if lastRelease.Info.Status == release.StatusDeployed {
// no need to retrieve the last deployed release from storage as the last release is deployed

@ -34,6 +34,8 @@ type Options struct {
StringValues []string
Values []string
FileValues []string
// Labels customized release secret/configmap labels
Labels []string
}
// MergeValues merges values from files specified via -f/--values and directly
@ -119,3 +121,18 @@ func readFile(filePath string, p getter.Providers) ([]byte, error) {
data, err := g.Get(filePath, getter.WithURL(filePath))
return data.Bytes(), err
}
func (opts *Options) ParseLabels() (map[string]string, error) {
base := map[string]interface{}{}
for _, l := range opts.Labels {
if err := strvals.ParseIntoString(l, base); err != nil {
return nil, errors.Wrap(err, "failed parsing --set-label data")
}
}
r := make(map[string]string)
for k, v := range base {
r[k] = v.(string)
}
return r, nil
}

@ -19,6 +19,8 @@ package values
import (
"reflect"
"testing"
"github.com/gosuri/uitable/util/strutil"
)
func TestMergeValues(t *testing.T) {
@ -75,3 +77,22 @@ func TestMergeValues(t *testing.T) {
t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap)
}
}
func TestParseLabels(t *testing.T) {
lbs := map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}
labels := make([]string, 0)
for k, v := range lbs {
labels = append(labels, strutil.Join([]string{k, v}, "="))
}
opts := &Options{
Labels: labels,
}
gotLbs, _ := opts.ParseLabels()
// compare created parsed labels with original
if !reflect.DeepEqual(lbs, gotLbs) {
t.Errorf("Expected {%v}, got {%v}", lbs, gotLbs)
}
}

@ -43,6 +43,8 @@ const ConfigMapsDriverName = "ConfigMap"
type ConfigMaps struct {
impl corev1.ConfigMapInterface
Log func(string, ...interface{})
// labels specifies the customized labels for release configmap
labels
}
// NewConfigMaps initializes a new ConfigMaps wrapping an implementation of
@ -157,6 +159,8 @@ func (cfgmaps *ConfigMaps) Create(key string, rls *rspb.Release) error {
var lbs labels
lbs.init()
// set customized labels
lbs.fromMap(cfgmaps.labels)
lbs.set("createdAt", strconv.Itoa(int(time.Now().Unix())))
// create a new configmap to hold the release
@ -183,7 +187,14 @@ func (cfgmaps *ConfigMaps) Update(key string, rls *rspb.Release) error {
// set labels for configmaps object meta data
var lbs labels
// get the existed configmap to re-use it's labels
exist, err := cfgmaps.impl.Get(context.Background(), key, metav1.GetOptions{})
if err != nil {
return errors.Wrapf(err, "update: failed to get configmap %q", key)
}
lbs.init()
lbs.fromMap(exist.Labels)
lbs.set("modifiedAt", strconv.Itoa(int(time.Now().Unix())))
// create a new configmap object to hold the release
@ -192,6 +203,7 @@ func (cfgmaps *ConfigMaps) Update(key string, rls *rspb.Release) error {
cfgmaps.Log("update: failed to encode release %q: %s", rls.Name, err)
return err
}
// push the configmap object out into the kubiverse
_, err = cfgmaps.impl.Update(context.Background(), obj, metav1.UpdateOptions{})
if err != nil {
@ -255,3 +267,30 @@ func newConfigMapsObject(key string, rls *rspb.Release, lbs labels) (*v1.ConfigM
Data: map[string]string{"release": s},
}, nil
}
// SetLabels set the customized labels, must be executed before Create function
func (cfgmaps *ConfigMaps) SetLabels(labels map[string]string) error {
if err := validate(labels); err != nil {
return err
}
if cfgmaps.labels == nil {
cfgmaps.labels.init()
}
cfgmaps.labels.fromMap(labels)
return nil
}
// GetLabels return the customized labels
func (cfgmaps *ConfigMaps) GetLabels(key string) (map[string]string, error) {
// fetch the configmap holding the release named by key
obj, err := cfgmaps.impl.Get(context.Background(), key, metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
return nil, ErrReleaseNotFound
}
cfgmaps.Log("get: failed to get %q: %s", key, err)
return nil, err
}
return retrieveCustomizedLabels(obj.Labels), nil
}

@ -38,7 +38,7 @@ func TestConfigMapGet(t *testing.T) {
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...)
cfgmaps := newTestFixtureCfgMaps(t, wrapReleases(rel)...)
// get release with key
got, err := cfgmaps.Get(key)
@ -84,14 +84,14 @@ func TestUncompressedConfigMapGet(t *testing.T) {
}
func TestConfigMapList(t *testing.T) {
cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{
cfgmaps := newTestFixtureCfgMaps(t, wrapReleases(
releaseStub("key-1", 1, "default", rspb.StatusUninstalled),
releaseStub("key-2", 1, "default", rspb.StatusUninstalled),
releaseStub("key-3", 1, "default", rspb.StatusDeployed),
releaseStub("key-4", 1, "default", rspb.StatusDeployed),
releaseStub("key-5", 1, "default", rspb.StatusSuperseded),
releaseStub("key-6", 1, "default", rspb.StatusSuperseded),
}...)
)...)
// list all deleted releases
del, err := cfgmaps.List(func(rel *rspb.Release) bool {
@ -131,14 +131,14 @@ func TestConfigMapList(t *testing.T) {
}
func TestConfigMapQuery(t *testing.T) {
cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{
cfgmaps := newTestFixtureCfgMaps(t, wrapReleases(
releaseStub("key-1", 1, "default", rspb.StatusUninstalled),
releaseStub("key-2", 1, "default", rspb.StatusUninstalled),
releaseStub("key-3", 1, "default", rspb.StatusDeployed),
releaseStub("key-4", 1, "default", rspb.StatusDeployed),
releaseStub("key-5", 1, "default", rspb.StatusSuperseded),
releaseStub("key-6", 1, "default", rspb.StatusSuperseded),
}...)
)...)
rls, err := cfgmaps.Query(map[string]string{"status": "deployed"})
if err != nil {
@ -180,6 +180,41 @@ func TestConfigMapCreate(t *testing.T) {
}
}
func TestConfigMapCreateWithLabels(t *testing.T) {
cfgmaps := newTestFixtureCfgMaps(t)
lbs := map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}
cfgmaps.SetLabels(lbs)
vers := 1
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
// store the release in a configmap
if err := cfgmaps.Create(key, rel); err != nil {
t.Fatalf("Failed to create release with key %q: %s", key, err)
}
// get the release back
got, err := cfgmaps.Get(key)
if err != nil {
t.Fatalf("Failed to get release with key %q: %s", key, err)
}
// compare created release with original
if !reflect.DeepEqual(rel, got) {
t.Errorf("Expected {%v}, got {%v}", rel, got)
}
// compare created configmap's labels with original
gotLbs, _ := cfgmaps.GetLabels(key)
if !reflect.DeepEqual(lbs, gotLbs) {
t.Errorf("Expected {%v}, got {%v}", lbs, gotLbs)
}
}
func TestConfigMapUpdate(t *testing.T) {
vers := 1
name := "smug-pigeon"
@ -187,7 +222,7 @@ func TestConfigMapUpdate(t *testing.T) {
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...)
cfgmaps := newTestFixtureCfgMaps(t, wrapReleases(rel)...)
// modify release status code
rel.Info.Status = rspb.StatusSuperseded
@ -209,6 +244,43 @@ func TestConfigMapUpdate(t *testing.T) {
}
}
func TestConfigMapUpdateWithLabels(t *testing.T) {
vers := 1
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
lbs := map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}
cfgmaps := newTestFixtureCfgMaps(t, []*releaseInfo{newReleaseInfoWithLabels(rel, deepCopyStringMap(lbs))}...)
// modify release status code
rel.Info.Status = rspb.StatusSuperseded
// perform the update
if err := cfgmaps.Update(key, rel); err != nil {
t.Fatalf("Failed to update release: %s", err)
}
// fetch the updated release
got, err := cfgmaps.Get(key)
if err != nil {
t.Fatalf("Failed to get release with key %q: %s", key, err)
}
// check release has actually been updated by comparing modified fields
if rel.Info.Status != got.Info.Status {
t.Errorf("Expected status %s, got status %s", rel.Info.Status.String(), got.Info.Status.String())
}
// compare created configmap's labels with original
gotLbs, _ := cfgmaps.GetLabels(key)
if !reflect.DeepEqual(lbs, gotLbs) {
t.Errorf("Expected {%v}, got {%v}", lbs, gotLbs)
}
}
func TestConfigMapDelete(t *testing.T) {
vers := 1
name := "smug-pigeon"
@ -216,7 +288,7 @@ func TestConfigMapDelete(t *testing.T) {
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...)
cfgmaps := newTestFixtureCfgMaps(t, wrapReleases(rel)...)
// perform the delete on a non-existent release
_, err := cfgmaps.Delete("nonexistent")
@ -239,3 +311,32 @@ func TestConfigMapDelete(t *testing.T) {
t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err)
}
}
func TestConfigMapSetLabels(t *testing.T) {
cfgmaps := newTestFixtureCfgMaps(t)
lbs := map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}
cfgmaps.SetLabels(lbs)
if !reflect.DeepEqual(lbs, cfgmaps.labels.toMap()) {
t.Errorf("Expected {%v}, got {%v}", lbs, cfgmaps.labels)
}
}
func TestConfigMapGetLabels(t *testing.T) {
vers := 1
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
lbs := map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}
cfgmaps := newTestFixtureCfgMaps(t, []*releaseInfo{newReleaseInfoWithLabels(rel, deepCopyStringMap(lbs))}...)
// compare created configmap's labels with original
gotLbs, _ := cfgmaps.GetLabels(key)
if !reflect.DeepEqual(lbs, gotLbs) {
t.Errorf("Expected {%v}, got {%v}", lbs, gotLbs)
}
}

@ -92,6 +92,16 @@ type Queryor interface {
Query(labels map[string]string) ([]*rspb.Release, error)
}
// Labelor is the interface that wraps the SetLabels and GetLabels methods.
//
// SetLabels label the release storage (like secret, configmap etc.) with given labels
//
// GetLabels get labels of the release storage (like secret, configmap etc.)
type Labelor interface {
SetLabels(labels map[string]string) error
GetLabels(key string) (map[string]string, error)
}
// Driver is the interface composed of Creator, Updator, Deletor, and Queryor
// interfaces. It defines the behavior for storing, updating, deleted,
// and retrieving Helm releases from some underlying storage mechanism,
@ -101,5 +111,6 @@ type Driver interface {
Updator
Deletor
Queryor
Labelor
Name() string
}

@ -16,9 +16,16 @@ limitations under the License.
package driver
import (
"fmt"
)
// labels is a map of key value pairs to be included as metadata in a configmap object.
type labels map[string]string
// reservedLabels specifies release secret/configmap labels for helm
var reservedLabels = []string{"createdAt", "modifiedAt", "name", "owner", "status", "version"}
func (lbs *labels) init() { *lbs = labels(make(map[string]string)) }
func (lbs labels) get(key string) string { return lbs[key] }
func (lbs labels) set(key, val string) { lbs[key] = val }
@ -46,3 +53,30 @@ func (lbs *labels) fromMap(kvs map[string]string) {
lbs.set(k, v)
}
}
// validate validates whether user set the labels using Helm preserved labels
func validate(labels map[string]string) error {
for _, lk := range reservedLabels {
if _, found := labels[lk]; found {
return fmt.Errorf("label key '%s' is reserved for helm, not available for users", lk)
}
}
return nil
}
// retrieveCustomizedLabels retrieves the real customized labels from the given labels that might contain Helm preserved labels
func retrieveCustomizedLabels(labels map[string]string) map[string]string {
copiedLabels := deepCopyStringMap(labels)
for _, lk := range reservedLabels {
delete(copiedLabels, lk)
}
return copiedLabels
}
func deepCopyStringMap(m map[string]string) map[string]string {
ret := make(map[string]string, len(m))
for k, v := range m {
ret[k] = v
}
return ret
}

@ -47,3 +47,48 @@ func TestLabelsMatch(t *testing.T) {
}
}
}
func TestValidate(t *testing.T) {
// empty map
var nilMap map[string]string
if err := validate(nilMap); err != nil {
t.Errorf("Nil label map should not fail when validating: %s", err)
}
// customized labels have no preserved labels of Helm
labels0 := map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}
if err := validate(labels0); err != nil {
t.Errorf("Customized labels with no preserved labels of Helm should not fail when validating: %s", err)
}
// customized labels contain preserved labels of Helm
labels1 := map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B", "owner": "Helm"}
if err := validate(labels1); err == nil {
t.Errorf("Customized labels with preserved labels of Helm must fail when validating")
}
}
func TestRetrieveCustomizedLabels(t *testing.T) {
// empty map
var nilMap map[string]string
customizedLabels := retrieveCustomizedLabels(nilMap)
if len(customizedLabels) != 0 {
t.Errorf("Nil label map retrieved result should be empy map")
}
// customized labels with no preserved labels of Helm
labels0 := map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}
customizedLabels = retrieveCustomizedLabels(labels0)
if len(customizedLabels) != len(labels0) {
t.Errorf("Customized labels with no preserved labels of Helm retrieved result should return the origin labels map")
}
// customized labels that every key in preserved labels of Helm
labels1 := map[string]string{"name": "name", "owner": "helm"}
customizedLabels = retrieveCustomizedLabels(labels1)
if len(customizedLabels) != 0 {
t.Errorf("customized labels that every key in preserved labels of Helm retrieved result should be empy map")
}
// customized labels contain preserved labels of Helm
labels2 := map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B", "owner": "Helm"}
customizedLabels = retrieveCustomizedLabels(labels2)
if len(customizedLabels) != 2 {
t.Errorf("customized labels contain preserved labels of Helm retrieved result should not contain Helm preserved label key")
}
}

@ -238,3 +238,13 @@ func (mem *Memory) rlock() func() {
// ```defer unlock(mem.rlock())```, locks mem for reading at the
// call point of defer and unlocks upon exiting the block.
func unlock(fn func()) { fn() }
func (mem *Memory) SetLabels(labels map[string]string) error {
// not support labels
return nil
}
func (mem *Memory) GetLabels(key string) (map[string]string, error) {
// not support labels
return nil, nil
}

@ -34,6 +34,33 @@ import (
rspb "helm.sh/helm/v3/pkg/release"
)
type releaseInfo struct {
release *rspb.Release
// labels specifies the customized labels for release storage's (secret/configmap)
labels map[string]string
}
func newReleaseInfo(rls *rspb.Release) *releaseInfo {
return &releaseInfo{
release: rls,
}
}
func wrapReleases(rlss ...*rspb.Release) []*releaseInfo {
rlsInfos := make([]*releaseInfo, 0)
for _, rls := range rlss {
rlsInfos = append(rlsInfos, newReleaseInfo(rls))
}
return rlsInfos
}
func newReleaseInfoWithLabels(rls *rspb.Release, lbs map[string]string) *releaseInfo {
return &releaseInfo{
release: rls,
labels: lbs,
}
}
func releaseStub(name string, vers int, namespace string, status rspb.Status) *rspb.Release {
return &rspb.Release{
Name: name,
@ -78,9 +105,9 @@ func tsFixtureMemory(t *testing.T) *Memory {
// newTestFixture initializes a MockConfigMapsInterface.
// ConfigMaps are created for each release provided.
func newTestFixtureCfgMaps(t *testing.T, releases ...*rspb.Release) *ConfigMaps {
func newTestFixtureCfgMaps(t *testing.T, rlsInfos ...*releaseInfo) *ConfigMaps {
var mock MockConfigMapsInterface
mock.Init(t, releases...)
mock.Init(t, rlsInfos...)
return NewConfigMaps(&mock)
}
@ -93,13 +120,13 @@ type MockConfigMapsInterface struct {
}
// Init initializes the MockConfigMapsInterface with the set of releases.
func (mock *MockConfigMapsInterface) Init(t *testing.T, releases ...*rspb.Release) {
func (mock *MockConfigMapsInterface) Init(t *testing.T, rlsInfos ...*releaseInfo) {
mock.objects = map[string]*v1.ConfigMap{}
for _, rls := range releases {
objkey := testKey(rls.Name, rls.Version)
for _, rlsInfo := range rlsInfos {
objkey := testKey(rlsInfo.release.Name, rlsInfo.release.Version)
cfgmap, err := newConfigMapsObject(objkey, rls, nil)
cfgmap, err := newConfigMapsObject(objkey, rlsInfo.release, rlsInfo.labels)
if err != nil {
t.Fatalf("Failed to create configmap: %s", err)
}
@ -164,9 +191,9 @@ func (mock *MockConfigMapsInterface) Delete(_ context.Context, name string, _ me
// newTestFixture initializes a MockSecretsInterface.
// Secrets are created for each release provided.
func newTestFixtureSecrets(t *testing.T, releases ...*rspb.Release) *Secrets {
func newTestFixtureSecrets(t *testing.T, rlsInfos ...*releaseInfo) *Secrets {
var mock MockSecretsInterface
mock.Init(t, releases...)
mock.Init(t, rlsInfos...)
return NewSecrets(&mock)
}
@ -179,13 +206,13 @@ type MockSecretsInterface struct {
}
// Init initializes the MockSecretsInterface with the set of releases.
func (mock *MockSecretsInterface) Init(t *testing.T, releases ...*rspb.Release) {
func (mock *MockSecretsInterface) Init(t *testing.T, rlsInfos ...*releaseInfo) {
mock.objects = map[string]*v1.Secret{}
for _, rls := range releases {
objkey := testKey(rls.Name, rls.Version)
for _, rlsInfo := range rlsInfos {
objkey := testKey(rlsInfo.release.Name, rlsInfo.release.Version)
secret, err := newSecretsObject(objkey, rls, nil)
secret, err := newSecretsObject(objkey, rlsInfo.release, rlsInfo.labels)
if err != nil {
t.Fatalf("Failed to create secret: %s", err)
}

@ -43,6 +43,8 @@ const SecretsDriverName = "Secret"
type Secrets struct {
impl corev1.SecretInterface
Log func(string, ...interface{})
// labels specifies the customized labels for release secret
labels
}
// NewSecrets initializes a new Secrets wrapping an implementation of
@ -148,6 +150,8 @@ func (secrets *Secrets) Create(key string, rls *rspb.Release) error {
var lbs labels
lbs.init()
// set customized labels
lbs.fromMap(secrets.labels)
lbs.set("createdAt", strconv.Itoa(int(time.Now().Unix())))
// create a new secret to hold the release
@ -172,7 +176,14 @@ func (secrets *Secrets) Update(key string, rls *rspb.Release) error {
// set labels for secrets object meta data
var lbs labels
// get the existed secret to re-use it's labels
exist, err := secrets.impl.Get(context.Background(), key, metav1.GetOptions{})
if err != nil {
return errors.Wrapf(err, "update: failed to get secret %q", key)
}
lbs.init()
lbs.fromMap(exist.Labels)
lbs.set("modifiedAt", strconv.Itoa(int(time.Now().Unix())))
// create a new secret object to hold the release
@ -180,6 +191,7 @@ func (secrets *Secrets) Update(key string, rls *rspb.Release) error {
if err != nil {
return errors.Wrapf(err, "update: failed to encode release %q", rls.Name)
}
// push the secret object out into the kubiverse
_, err = secrets.impl.Update(context.Background(), obj, metav1.UpdateOptions{})
return errors.Wrap(err, "update: failed to update")
@ -248,3 +260,28 @@ func newSecretsObject(key string, rls *rspb.Release, lbs labels) (*v1.Secret, er
Data: map[string][]byte{"release": []byte(s)},
}, nil
}
// SetLabels set the customized labels, must be executed before Create function
func (secrets *Secrets) SetLabels(labels map[string]string) error {
if err := validate(labels); err != nil {
return err
}
if secrets.labels == nil {
secrets.labels.init()
}
secrets.labels.fromMap(labels)
return nil
}
// GetLabels return the customized labels
func (secrets *Secrets) GetLabels(key string) (map[string]string, error) {
// fetch the secret holding the release named by key
obj, err := secrets.impl.Get(context.Background(), key, metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
return nil, ErrReleaseNotFound
}
return nil, errors.Wrapf(err, "get: failed to get %q", key)
}
return retrieveCustomizedLabels(obj.Labels), nil
}

@ -38,7 +38,7 @@ func TestSecretGet(t *testing.T) {
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...)
secrets := newTestFixtureSecrets(t, wrapReleases(rel)...)
// get release with key
got, err := secrets.Get(key)
@ -51,7 +51,7 @@ func TestSecretGet(t *testing.T) {
}
}
func TestUNcompressedSecretGet(t *testing.T) {
func TestUncompressedSecretGet(t *testing.T) {
vers := 1
name := "smug-pigeon"
namespace := "default"
@ -84,14 +84,14 @@ func TestUNcompressedSecretGet(t *testing.T) {
}
func TestSecretList(t *testing.T) {
secrets := newTestFixtureSecrets(t, []*rspb.Release{
secrets := newTestFixtureSecrets(t, wrapReleases(
releaseStub("key-1", 1, "default", rspb.StatusUninstalled),
releaseStub("key-2", 1, "default", rspb.StatusUninstalled),
releaseStub("key-3", 1, "default", rspb.StatusDeployed),
releaseStub("key-4", 1, "default", rspb.StatusDeployed),
releaseStub("key-5", 1, "default", rspb.StatusSuperseded),
releaseStub("key-6", 1, "default", rspb.StatusSuperseded),
}...)
)...)
// list all deleted releases
del, err := secrets.List(func(rel *rspb.Release) bool {
@ -131,14 +131,14 @@ func TestSecretList(t *testing.T) {
}
func TestSecretQuery(t *testing.T) {
secrets := newTestFixtureSecrets(t, []*rspb.Release{
secrets := newTestFixtureSecrets(t, wrapReleases(
releaseStub("key-1", 1, "default", rspb.StatusUninstalled),
releaseStub("key-2", 1, "default", rspb.StatusUninstalled),
releaseStub("key-3", 1, "default", rspb.StatusDeployed),
releaseStub("key-4", 1, "default", rspb.StatusDeployed),
releaseStub("key-5", 1, "default", rspb.StatusSuperseded),
releaseStub("key-6", 1, "default", rspb.StatusSuperseded),
}...)
)...)
rls, err := secrets.Query(map[string]string{"status": "deployed"})
if err != nil {
@ -180,6 +180,41 @@ func TestSecretCreate(t *testing.T) {
}
}
func TestSecretCreateWithLabels(t *testing.T) {
secrets := newTestFixtureSecrets(t)
lbs := map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}
secrets.SetLabels(lbs)
vers := 1
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
// store the release in a secret
if err := secrets.Create(key, rel); err != nil {
t.Fatalf("Failed to create release with key %q: %s", key, err)
}
// get the release back
got, err := secrets.Get(key)
if err != nil {
t.Fatalf("Failed to get release with key %q: %s", key, err)
}
// compare created release with original
if !reflect.DeepEqual(rel, got) {
t.Errorf("Expected {%v}, got {%v}", rel, got)
}
// compare created secret's labels with original
gotLbs, _ := secrets.GetLabels(key)
if !reflect.DeepEqual(lbs, gotLbs) {
t.Errorf("Expected {%v}, got {%v}", lbs, gotLbs)
}
}
func TestSecretUpdate(t *testing.T) {
vers := 1
name := "smug-pigeon"
@ -187,7 +222,7 @@ func TestSecretUpdate(t *testing.T) {
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...)
secrets := newTestFixtureSecrets(t, wrapReleases(rel)...)
// modify release status code
rel.Info.Status = rspb.StatusSuperseded
@ -209,6 +244,43 @@ func TestSecretUpdate(t *testing.T) {
}
}
func TestSecretUpdateWithLabels(t *testing.T) {
vers := 1
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
lbs := map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}
secrets := newTestFixtureSecrets(t, []*releaseInfo{newReleaseInfoWithLabels(rel, deepCopyStringMap(lbs))}...)
// modify release status code
rel.Info.Status = rspb.StatusSuperseded
// perform the update
if err := secrets.Update(key, rel); err != nil {
t.Fatalf("Failed to update release: %s", err)
}
// fetch the updated release
got, err := secrets.Get(key)
if err != nil {
t.Fatalf("Failed to get release with key %q: %s", key, err)
}
// check release has actually been updated by comparing modified fields
if rel.Info.Status != got.Info.Status {
t.Errorf("Expected status %s, got status %s", rel.Info.Status.String(), got.Info.Status.String())
}
// compare created secret's labels with original
gotLbs, _ := secrets.GetLabels(key)
if !reflect.DeepEqual(lbs, gotLbs) {
t.Errorf("Expected {%v}, got {%v}", lbs, gotLbs)
}
}
func TestSecretDelete(t *testing.T) {
vers := 1
name := "smug-pigeon"
@ -216,7 +288,7 @@ func TestSecretDelete(t *testing.T) {
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...)
secrets := newTestFixtureSecrets(t, wrapReleases(rel)...)
// perform the delete on a non-existing release
_, err := secrets.Delete("nonexistent")
@ -239,3 +311,32 @@ func TestSecretDelete(t *testing.T) {
t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err)
}
}
func TestSecretSetLabels(t *testing.T) {
secrets := newTestFixtureSecrets(t)
lbs := map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}
secrets.SetLabels(lbs)
if !reflect.DeepEqual(lbs, secrets.labels.toMap()) {
t.Errorf("Expected {%v}, got {%v}", lbs, secrets.labels)
}
}
func TestSecretGetLabels(t *testing.T) {
vers := 1
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
lbs := map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}
secrets := newTestFixtureSecrets(t, []*releaseInfo{newReleaseInfoWithLabels(rel, deepCopyStringMap(lbs))}...)
// compare created secret's labels with original
gotLbs, _ := secrets.GetLabels(key)
if !reflect.DeepEqual(lbs, gotLbs) {
t.Errorf("Expected {%v}, got {%v}", lbs, gotLbs)
}
}

@ -494,3 +494,13 @@ func (s *SQL) Delete(key string) (*rspb.Release, error) {
_, err = transaction.Exec(deleteQuery, args...)
return release, err
}
func (s *SQL) SetLabels(labels map[string]string) error {
// not support labels
return nil
}
func (s *SQL) GetLabels(key string) (map[string]string, error) {
// not support labels
return nil, nil
}

@ -54,6 +54,11 @@ func (s *Storage) Get(name string, version int) (*rspb.Release, error) {
return s.Driver.Get(makeKey(name, version))
}
// GetLabels retrieves the labels of release storage.
func (s *Storage) GetLabels(name string, version int) (map[string]string, error) {
return s.Driver.GetLabels(makeKey(name, version))
}
// Create creates a new storage entry holding the release. An
// error is returned if the storage driver fails to store the
// release, or a release with an identical key already exists.

Loading…
Cancel
Save