Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support to set labels without selector #5741

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion kustomize/commands/edit/add/addmetadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func newCmdAddLabel(fSys filesys.FileSystem, v func(map[string]string) error) *c
o.mapValidator = v
cmd := &cobra.Command{
Use: "label",
Short: "Adds one or more commonLabels to " +
Short: "Adds one or more commonLabels or labels to " +
konfig.DefaultKustomizationFileName(),
Example: `
add label {labelKey1:labelValue1} {labelKey2:labelValue2}`,
Expand Down
67 changes: 64 additions & 3 deletions kustomize/commands/edit/set/setlabel.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ import (
)

type setLabelOptions struct {
metadata map[string]string
mapValidator func(map[string]string) error
metadata map[string]string
mapValidator func(map[string]string) error
labelsWithoutSelector bool
includeTemplates bool
}

// newCmdSetLabel sets one or more commonLabels to the kustomization file.
Expand All @@ -25,14 +27,20 @@ func newCmdSetLabel(fSys filesys.FileSystem, v func(map[string]string) error) *c
o.mapValidator = v
cmd := &cobra.Command{
Use: "label",
Short: "Sets one or more commonLabels in " +
Short: "Sets one or more commonLabels or labels in " +
konfig.DefaultKustomizationFileName(),
Example: `
set label {labelKey1:labelValue1} {labelKey2:labelValue2}`,
RunE: func(cmd *cobra.Command, args []string) error {
return o.runE(args, fSys, o.setLabels)
},
}
cmd.Flags().BoolVar(&o.labelsWithoutSelector, "without-selector", false,
"using set labels without selector option",
)
cmd.Flags().BoolVar(&o.includeTemplates, "include-templates", false,
"include labels in templates (requires --without-selector)",
)
return cmd
}

Expand Down Expand Up @@ -62,6 +70,9 @@ func (o *setLabelOptions) validateAndParse(args []string) error {
if len(args) < 1 {
return fmt.Errorf("must specify label")
}
if !o.labelsWithoutSelector && o.includeTemplates {
return fmt.Errorf("--without-selector flag must be specified for --include-templates to work")
}
m, err := util.ConvertSliceToMap(args, "label")
if err != nil {
return err
Expand All @@ -74,9 +85,35 @@ func (o *setLabelOptions) validateAndParse(args []string) error {
}

func (o *setLabelOptions) setLabels(m *types.Kustomization) error {
o.removeDuplicateLabels(m)
if o.labelsWithoutSelector {
var labelPairs *types.Label
for _, label := range m.Labels {
if !label.IncludeSelectors && label.IncludeTemplates == o.includeTemplates {
labelPairs = &label
break
}
}

if labelPairs != nil {
if labelPairs.Pairs == nil {
labelPairs.Pairs = make(map[string]string)
}
return o.writeToMap(labelPairs.Pairs)
}

m.Labels = append(m.Labels, types.Label{
Pairs: make(map[string]string),
IncludeSelectors: false,
IncludeTemplates: o.includeTemplates,
})
return o.writeToMap(m.Labels[len(m.Labels)-1].Pairs)
}

if m.CommonLabels == nil {
m.CommonLabels = make(map[string]string)
}

return o.writeToMap(m.CommonLabels)
}

Expand All @@ -86,3 +123,27 @@ func (o *setLabelOptions) writeToMap(m map[string]string) error {
}
return nil
}

// removeDuplicateLabels removes duplicate labels from commonLabels or labels
func (o *setLabelOptions) removeDuplicateLabels(m *types.Kustomization) {
for k := range o.metadata {
// delete duplicate label from deprecated common labels
delete(m.CommonLabels, k)
for idx, label := range m.Labels {
// delete label if it's already present in labels
if _, found := label.Pairs[k]; found {
m.Labels = deleteLabel(k, label, m.Labels, idx)
}
}
}
}

// deleteLabel deletes label from types.Label
func deleteLabel(key string, label types.Label, labels []types.Label, idx int) []types.Label {
delete(label.Pairs, key)
if len(label.Pairs) == 0 {
// remove empty map label.Pairs from labels
labels = append(labels[:idx], labels[idx+1:]...)
}
return labels
}
75 changes: 75 additions & 0 deletions kustomize/commands/edit/set/setlabel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package set
import (
"testing"

"github.com/stretchr/testify/require"
valtest_test "sigs.k8s.io/kustomize/api/testutils/valtest"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kustomize/v5/commands/internal/kustfile"
Expand Down Expand Up @@ -152,3 +153,77 @@ func TestSetLabelExisting(t *testing.T) {
t.Errorf("unexpected error: %v", err.Error())
}
}

func TestSetLabelWithoutSelector(t *testing.T) {
var o setLabelOptions
o.metadata = map[string]string{"key1": "foo", "key2": "bar"}
o.labelsWithoutSelector = true

m := makeKustomization(t)
require.NoError(t, o.setLabels(m))
require.Equal(t, m.Labels[0], types.Label{Pairs: map[string]string{"key1": "foo", "key2": "bar"}})
}

func TestSetLabelWithoutSelectorWithExistingLabels(t *testing.T) {
var o setLabelOptions
o.metadata = map[string]string{"key1": "foo", "key2": "bar"}
o.labelsWithoutSelector = true

m := makeKustomization(t)
require.NoError(t, o.setLabels(m))
require.Equal(t, m.Labels[0], types.Label{Pairs: map[string]string{"key1": "foo", "key2": "bar"}})

o.metadata = map[string]string{"key3": "foobar"}
require.NoError(t, o.setLabels(m))
require.Equal(t, m.Labels[0], types.Label{Pairs: map[string]string{"key1": "foo", "key2": "bar", "key3": "foobar"}})
}

func TestSetLabelWithoutSelectorWithDuplicateLabel(t *testing.T) {
var o setLabelOptions
o.metadata = map[string]string{"key1": "foo", "key2": "bar"}
o.labelsWithoutSelector = true
o.includeTemplates = true

m := makeKustomization(t)
require.NoError(t, o.setLabels(m))
require.Equal(t, m.Labels[0], types.Label{Pairs: map[string]string{"key1": "foo", "key2": "bar"}, IncludeTemplates: true})

o.metadata = map[string]string{"key2": "bar"}
o.includeTemplates = false
require.NoError(t, o.setLabels(m))
require.Equal(t, m.Labels[0], types.Label{Pairs: map[string]string{"key1": "foo"}, IncludeTemplates: true})
require.Equal(t, m.Labels[1], types.Label{Pairs: map[string]string{"key2": "bar"}})
}

func TestSetLabelWithoutSelectorWithCommonLabel(t *testing.T) {
var o setLabelOptions
o.metadata = map[string]string{"key1": "foo", "key2": "bar"}

m := makeKustomization(t)
require.NoError(t, o.setLabels(m))
require.Empty(t, m.Labels)
require.Equal(t, m.CommonLabels, map[string]string{"app": "helloworld", "key1": "foo", "key2": "bar"})

o.metadata = map[string]string{"key2": "bar"}
o.labelsWithoutSelector = true
require.NoError(t, o.setLabels(m))
require.Equal(t, m.CommonLabels, map[string]string{"app": "helloworld", "key1": "foo"})
require.Equal(t, m.Labels[0], types.Label{Pairs: map[string]string{"key2": "bar"}})
}

func TestSetLabelCommonLabelWithWithoutSelector(t *testing.T) {
var o setLabelOptions
o.metadata = map[string]string{"key1": "foo", "key2": "bar"}

m := makeKustomization(t)
o.labelsWithoutSelector = true
require.NoError(t, o.setLabels(m))
require.Equal(t, m.Labels[0], types.Label{Pairs: map[string]string{"key1": "foo", "key2": "bar"}})
require.Equal(t, m.CommonLabels, map[string]string{"app": "helloworld"})

o.metadata = map[string]string{"key2": "bar2"}
o.labelsWithoutSelector = false
require.NoError(t, o.setLabels(m))
require.Equal(t, m.Labels[0], types.Label{Pairs: map[string]string{"key1": "foo"}})
require.Equal(t, m.CommonLabels, map[string]string{"app": "helloworld", "key2": "bar2"})
}