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 1 commit
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
72 changes: 69 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,36 @@ func (o *setLabelOptions) validateAndParse(args []string) error {
}

func (o *setLabelOptions) setLabels(m *types.Kustomization) error {
if o.labelsWithoutSelector {
o.removeDuplicateLabels(m)

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 +124,31 @@ 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 with mismatched includeTemplates value
if label.IncludeTemplates != o.includeTemplates {
m.Labels = deleteLabel(k, label, m.Labels, idx)
}
if label.IncludeSelectors {
// delete label if it's already present in labels and includes selectors
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
}
58 changes: 58 additions & 0 deletions kustomize/commands/edit/set/setlabel_test.go
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any specific reason to use assert instead of require for the Equal assertions?

Copy link
Author

@Mrflatt Mrflatt Aug 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, all of these should be require, will change all of them.

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,60 @@ 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"}})
}