Skip to content

Commit a22f5fb

Browse files
committed
Ignore files with the LocalConfig annotation
1 parent 41a715c commit a22f5fb

File tree

6 files changed

+163
-0
lines changed

6 files changed

+163
-0
lines changed

pkg/config/initoptions.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
cmdutil "k8s.io/kubectl/pkg/cmd/util"
1717
"sigs.k8s.io/cli-utils/pkg/common"
1818
"sigs.k8s.io/kustomize/kyaml/kio"
19+
"sigs.k8s.io/kustomize/kyaml/kio/filters"
1920
)
2021

2122
const (
@@ -177,6 +178,13 @@ func allInSameNamespace(packageDir string) (string, bool, error) {
177178
if err != nil {
178179
return "", false, err
179180
}
181+
182+
// Filter out any resources with the LocalConfig annotation
183+
nodes, err = (&filters.IsLocalConfig{}).Filter(nodes)
184+
if err != nil {
185+
return "", false, err
186+
}
187+
180188
var ns string
181189
for _, node := range nodes {
182190
rm, err := node.GetMeta()

pkg/config/initoptions_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ metadata:
4848
name: objC
4949
`)
5050

51+
var readFileD = []byte(`
52+
apiVersion: v1
53+
kind: Pod
54+
metadata:
55+
name: objD
56+
namespace: namespaceD
57+
annotations:
58+
config.kubernetes.io/local-config: "true"
59+
`)
60+
5161
func TestComplete(t *testing.T) {
5262
tests := map[string]struct {
5363
args []string
@@ -97,6 +107,23 @@ func TestComplete(t *testing.T) {
97107
isError: false,
98108
expectedNamespace: "foo",
99109
},
110+
"Resources with the LocalConfig annotation should be ignored": {
111+
args: []string{},
112+
files: map[string][]byte{
113+
"b_test.yaml": readFileB,
114+
"d_test.yaml": readFileD,
115+
},
116+
isError: false,
117+
expectedNamespace: "namespaceB",
118+
},
119+
"If all resources have the LocalConfig annotation use the default namespace": {
120+
args: []string{},
121+
files: map[string][]byte{
122+
"d_test.yaml": readFileD,
123+
},
124+
isError: false,
125+
expectedNamespace: "foo",
126+
},
100127
}
101128
for name, tc := range tests {
102129
t.Run(name, func(t *testing.T) {

pkg/manifestreader/common.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"k8s.io/kubectl/pkg/cmd/util"
1313
"sigs.k8s.io/cli-utils/pkg/apply/solver"
1414
"sigs.k8s.io/cli-utils/pkg/inventory"
15+
"sigs.k8s.io/kustomize/kyaml/kio/filters"
1516
)
1617

1718
// ManifestReader defines the interface for reading a set
@@ -122,3 +123,19 @@ func setNamespaces(factory util.Factory, infos []*resource.Info,
122123

123124
return nil
124125
}
126+
127+
// filterLocalConfig returns a new slice of infos where all resources
128+
// with the LocalConfig annotation is filtered out.
129+
func filterLocalConfig(infos []*resource.Info) []*resource.Info {
130+
var filterInfos []*resource.Info
131+
for _, inf := range infos {
132+
acc, _ := meta.Accessor(inf.Object)
133+
// Ignoring the value of the LocalConfigAnnotation here. This is to be
134+
// consistent with the behavior in the kyaml library:
135+
// https://github.com/kubernetes-sigs/kustomize/blob/30b58e90a39485bc5724b2278651c5d26b815cb2/kyaml/kio/filters/local.go#L29
136+
if _, found := acc.GetAnnotations()[filters.LocalConfigAnnotation]; !found {
137+
filterInfos = append(filterInfos, inf)
138+
}
139+
}
140+
return filterInfos
141+
}

pkg/manifestreader/common_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package manifestreader
55

66
import (
7+
"fmt"
78
"testing"
89

910
"github.com/stretchr/testify/assert"
@@ -14,6 +15,8 @@ import (
1415
"k8s.io/cli-runtime/pkg/resource"
1516
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
1617
"k8s.io/kubectl/pkg/scheme"
18+
"sigs.k8s.io/cli-utils/pkg/object"
19+
"sigs.k8s.io/kustomize/kyaml/kio/filters"
1720
)
1821

1922
func TestSetNamespaces(t *testing.T) {
@@ -154,6 +157,78 @@ func TestSetNamespaces(t *testing.T) {
154157
}
155158
}
156159

160+
var (
161+
depID = object.ObjMetadata{
162+
GroupKind: schema.GroupKind{
163+
Group: "apps",
164+
Kind: "Deployment",
165+
},
166+
Namespace: "default",
167+
Name: "foo",
168+
}
169+
170+
clusterRoleID = object.ObjMetadata{
171+
GroupKind: schema.GroupKind{
172+
Group: "rbac.authorization.k8s.io",
173+
Kind: "ClusterRole",
174+
},
175+
Name: "bar",
176+
}
177+
)
178+
179+
func TestFilterLocalConfigs(t *testing.T) {
180+
testCases := map[string]struct {
181+
input []*resource.Info
182+
expected []string
183+
}{
184+
"don't filter if no annotation": {
185+
input: []*resource.Info{
186+
objMetaToInfo(depID),
187+
objMetaToInfo(clusterRoleID),
188+
},
189+
expected: []string{
190+
depID.Name,
191+
clusterRoleID.Name,
192+
},
193+
},
194+
"filter all if all have annotation": {
195+
input: []*resource.Info{
196+
addAnnotation(t, objMetaToInfo(depID), filters.LocalConfigAnnotation, "true"),
197+
addAnnotation(t, objMetaToInfo(clusterRoleID), filters.LocalConfigAnnotation, "false"),
198+
},
199+
expected: []string{},
200+
},
201+
"filter even if resource have other annotations": {
202+
input: []*resource.Info{
203+
addAnnotation(t,
204+
addAnnotation(
205+
t, objMetaToInfo(depID),
206+
filters.LocalConfigAnnotation, "true"),
207+
"my-annotation", "foo"),
208+
},
209+
expected: []string{},
210+
},
211+
}
212+
213+
for tn, tc := range testCases {
214+
t.Run(tn, func(t *testing.T) {
215+
res := filterLocalConfig(tc.input)
216+
217+
var names []string
218+
for _, inf := range res {
219+
names = append(names, inf.Name)
220+
}
221+
222+
// Avoid test failures due to nil slice and empty slice
223+
// not being equal.
224+
if len(tc.expected) == 0 && len(names) == 0 {
225+
return
226+
}
227+
assert.Equal(t, tc.expected, names)
228+
})
229+
}
230+
}
231+
157232
func toInfo(gvk schema.GroupVersionKind, namespace string) *resource.Info {
158233
return &resource.Info{
159234
Namespace: namespace,
@@ -187,3 +262,37 @@ func toCRDInfo(gvk schema.GroupVersionKind, gk schema.GroupKind,
187262
},
188263
}
189264
}
265+
266+
func objMetaToInfo(id object.ObjMetadata) *resource.Info {
267+
return &resource.Info{
268+
Namespace: id.Namespace,
269+
Name: id.Name,
270+
Object: &unstructured.Unstructured{
271+
Object: map[string]interface{}{
272+
"apiVersion": fmt.Sprintf("%s/v1", id.GroupKind.Group),
273+
"kind": id.GroupKind.Kind,
274+
"metadata": map[string]interface{}{
275+
"namespace": id.Namespace,
276+
"name": id.Name,
277+
},
278+
},
279+
},
280+
}
281+
}
282+
283+
func addAnnotation(t *testing.T, info *resource.Info, name, val string) *resource.Info {
284+
u := info.Object.(*unstructured.Unstructured)
285+
annos, found, err := unstructured.NestedStringMap(u.Object, "metadata", "annotations")
286+
if err != nil {
287+
t.Fatal(err)
288+
}
289+
if !found {
290+
annos = make(map[string]string)
291+
}
292+
annos[name] = val
293+
err = unstructured.SetNestedStringMap(u.Object, annos, "metadata", "annotations")
294+
if err != nil {
295+
t.Fatal(err)
296+
}
297+
return info
298+
}

pkg/manifestreader/path.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ func (p *PathManifestReader) Read() ([]*resource.Info, error) {
4545
if err != nil {
4646
return nil, err
4747
}
48+
infos = filterLocalConfig(infos)
4849

4950
err = setNamespaces(p.Factory, infos, p.Namespace, p.EnforceNamespace)
5051
if err != nil {

pkg/manifestreader/stream.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ func (r *StreamManifestReader) Read() ([]*resource.Info, error) {
4242
if err != nil {
4343
return nil, err
4444
}
45+
infos = filterLocalConfig(infos)
4546

4647
err = setNamespaces(r.Factory, infos, r.Namespace, r.EnforceNamespace)
4748
if err != nil {

0 commit comments

Comments
 (0)