Skip to content

Commit 5e888be

Browse files
authored
determine k8s namespace scope (#568)
1 parent 3a8c6c2 commit 5e888be

File tree

3 files changed

+169
-0
lines changed

3 files changed

+169
-0
lines changed

go/fn/internal/const/namespace.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package _const
2+
3+
import (
4+
"sigs.k8s.io/kustomize/kyaml/yaml"
5+
)
6+
7+
// precomputedIsNamespaceScoped copies the sigs.k8s.io/kustomize/kyaml/openapi precomputedIsNamespaceScoped
8+
var PrecomputedIsNamespaceScoped = map[yaml.TypeMeta]bool{
9+
{APIVersion: "admissionregistration.k8s.io/v1", Kind: "MutatingWebhookConfiguration"}: false,
10+
{APIVersion: "admissionregistration.k8s.io/v1", Kind: "ValidatingWebhookConfiguration"}: false,
11+
{APIVersion: "admissionregistration.k8s.io/v1beta1", Kind: "MutatingWebhookConfiguration"}: false,
12+
{APIVersion: "admissionregistration.k8s.io/v1beta1", Kind: "ValidatingWebhookConfiguration"}: false,
13+
{APIVersion: "apiextensions.k8s.io/v1", Kind: "CustomResourceDefinition"}: false,
14+
{APIVersion: "apiextensions.k8s.io/v1beta1", Kind: "CustomResourceDefinition"}: false,
15+
{APIVersion: "apiregistration.k8s.io/v1", Kind: "APIService"}: false,
16+
{APIVersion: "apiregistration.k8s.io/v1beta1", Kind: "APIService"}: false,
17+
{APIVersion: "apps/v1", Kind: "ControllerRevision"}: true,
18+
{APIVersion: "apps/v1", Kind: "DaemonSet"}: true,
19+
{APIVersion: "apps/v1", Kind: "Deployment"}: true,
20+
{APIVersion: "apps/v1", Kind: "ReplicaSet"}: true,
21+
{APIVersion: "apps/v1", Kind: "StatefulSet"}: true,
22+
{APIVersion: "autoscaling/v1", Kind: "HorizontalPodAutoscaler"}: true,
23+
{APIVersion: "autoscaling/v1", Kind: "Scale"}: true,
24+
{APIVersion: "autoscaling/v2beta1", Kind: "HorizontalPodAutoscaler"}: true,
25+
{APIVersion: "autoscaling/v2beta2", Kind: "HorizontalPodAutoscaler"}: true,
26+
{APIVersion: "batch/v1", Kind: "CronJob"}: true,
27+
{APIVersion: "batch/v1", Kind: "Job"}: true,
28+
{APIVersion: "batch/v1beta1", Kind: "CronJob"}: true,
29+
{APIVersion: "certificates.k8s.io/v1", Kind: "CertificateSigningRequest"}: false,
30+
{APIVersion: "certificates.k8s.io/v1beta1", Kind: "CertificateSigningRequest"}: false,
31+
{APIVersion: "coordination.k8s.io/v1", Kind: "Lease"}: true,
32+
{APIVersion: "coordination.k8s.io/v1beta1", Kind: "Lease"}: true,
33+
{APIVersion: "discovery.k8s.io/v1", Kind: "EndpointSlice"}: true,
34+
{APIVersion: "discovery.k8s.io/v1beta1", Kind: "EndpointSlice"}: true,
35+
{APIVersion: "events.k8s.io/v1", Kind: "Event"}: true,
36+
{APIVersion: "events.k8s.io/v1beta1", Kind: "Event"}: true,
37+
{APIVersion: "extensions/v1beta1", Kind: "Ingress"}: true,
38+
{APIVersion: "flowcontrol.apiserver.k8s.io/v1beta1", Kind: "FlowSchema"}: false,
39+
{APIVersion: "flowcontrol.apiserver.k8s.io/v1beta1", Kind: "PriorityLevelConfiguration"}: false,
40+
{APIVersion: "networking.k8s.io/v1", Kind: "Ingress"}: true,
41+
{APIVersion: "networking.k8s.io/v1", Kind: "IngressClass"}: false,
42+
{APIVersion: "networking.k8s.io/v1", Kind: "NetworkPolicy"}: true,
43+
{APIVersion: "networking.k8s.io/v1beta1", Kind: "Ingress"}: true,
44+
{APIVersion: "networking.k8s.io/v1beta1", Kind: "IngressClass"}: false,
45+
{APIVersion: "node.k8s.io/v1", Kind: "RuntimeClass"}: false,
46+
{APIVersion: "node.k8s.io/v1beta1", Kind: "RuntimeClass"}: false,
47+
{APIVersion: "policy/v1", Kind: "PodDisruptionBudget"}: true,
48+
{APIVersion: "policy/v1beta1", Kind: "PodDisruptionBudget"}: true,
49+
{APIVersion: "policy/v1beta1", Kind: "PodSecurityPolicy"}: false,
50+
{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole"}: false,
51+
{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRoleBinding"}: false,
52+
{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "Role"}: true,
53+
{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "RoleBinding"}: true,
54+
{APIVersion: "rbac.authorization.k8s.io/v1beta1", Kind: "ClusterRole"}: false,
55+
{APIVersion: "rbac.authorization.k8s.io/v1beta1", Kind: "ClusterRoleBinding"}: false,
56+
{APIVersion: "rbac.authorization.k8s.io/v1beta1", Kind: "Role"}: true,
57+
{APIVersion: "rbac.authorization.k8s.io/v1beta1", Kind: "RoleBinding"}: true,
58+
{APIVersion: "scheduling.k8s.io/v1", Kind: "PriorityClass"}: false,
59+
{APIVersion: "scheduling.k8s.io/v1beta1", Kind: "PriorityClass"}: false,
60+
{APIVersion: "storage.k8s.io/v1", Kind: "CSIDriver"}: false,
61+
{APIVersion: "storage.k8s.io/v1", Kind: "CSINode"}: false,
62+
{APIVersion: "storage.k8s.io/v1", Kind: "StorageClass"}: false,
63+
{APIVersion: "storage.k8s.io/v1", Kind: "VolumeAttachment"}: false,
64+
{APIVersion: "storage.k8s.io/v1beta1", Kind: "CSIDriver"}: false,
65+
{APIVersion: "storage.k8s.io/v1beta1", Kind: "CSINode"}: false,
66+
{APIVersion: "storage.k8s.io/v1beta1", Kind: "CSIStorageCapacity"}: true,
67+
{APIVersion: "storage.k8s.io/v1beta1", Kind: "StorageClass"}: false,
68+
{APIVersion: "storage.k8s.io/v1beta1", Kind: "VolumeAttachment"}: false,
69+
{APIVersion: "v1", Kind: "ComponentStatus"}: false,
70+
{APIVersion: "v1", Kind: "ConfigMap"}: true,
71+
{APIVersion: "v1", Kind: "Endpoints"}: true,
72+
{APIVersion: "v1", Kind: "Event"}: true,
73+
{APIVersion: "v1", Kind: "LimitRange"}: true,
74+
{APIVersion: "v1", Kind: "Namespace"}: false,
75+
{APIVersion: "v1", Kind: "Node"}: false,
76+
{APIVersion: "v1", Kind: "NodeProxyOptions"}: false,
77+
{APIVersion: "v1", Kind: "PersistentVolume"}: false,
78+
{APIVersion: "v1", Kind: "PersistentVolumeClaim"}: true,
79+
{APIVersion: "v1", Kind: "Pod"}: true,
80+
{APIVersion: "v1", Kind: "PodAttachOptions"}: true,
81+
{APIVersion: "v1", Kind: "PodExecOptions"}: true,
82+
{APIVersion: "v1", Kind: "PodPortForwardOptions"}: true,
83+
{APIVersion: "v1", Kind: "PodProxyOptions"}: true,
84+
{APIVersion: "v1", Kind: "PodTemplate"}: true,
85+
{APIVersion: "v1", Kind: "ReplicationController"}: true,
86+
{APIVersion: "v1", Kind: "ResourceQuota"}: true,
87+
{APIVersion: "v1", Kind: "Secret"}: true,
88+
{APIVersion: "v1", Kind: "Service"}: true,
89+
{APIVersion: "v1", Kind: "ServiceAccount"}: true,
90+
{APIVersion: "v1", Kind: "ServiceProxyOptions"}: true,
91+
}

go/fn/object.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"strconv"
2121

2222
"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn/internal"
23+
_const "github.com/GoogleContainerTools/kpt-functions-sdk/go/fn/internal/const"
2324
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
2425
"sigs.k8s.io/kustomize/kyaml/yaml"
2526
)
@@ -551,6 +552,22 @@ func (o *KubeObject) GetNamespace() string {
551552
return s
552553
}
553554

555+
// IsNamespaceScoped tells whether a k8s resource is namespace scoped. If the KubeObject resource is a customized, it
556+
// determines the namespace scope by checking whether `metadata.namespace` is set.
557+
func (o *KubeObject) IsNamespaceScoped() bool {
558+
tm := yaml.TypeMeta{Kind: o.GetKind(), APIVersion: o.GetAPIVersion()}
559+
if nsScoped, ok := _const.PrecomputedIsNamespaceScoped[tm]; ok {
560+
return nsScoped
561+
}
562+
// TODO(yuwenma): parse the resource openapi schema to know its scope status.
563+
return o.HasNamespace()
564+
}
565+
566+
// IsClusterScoped tells whether a resource is cluster scoped.
567+
func (o *KubeObject) IsClusterScoped() bool {
568+
return !o.IsNamespaceScoped()
569+
}
570+
554571
func (o *KubeObject) HasNamespace() bool {
555572
_, found, _ := o.obj.GetNestedString("metadata", "namespace")
556573
return found

go/fn/object_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package fn
2+
3+
import "testing"
4+
5+
func TestIsNamespaceScoped(t *testing.T) {
6+
testdata := map[string]struct{
7+
input []byte
8+
expected bool
9+
}{
10+
"k8s resource, namespace scoped but unset": {
11+
input: []byte(`
12+
apiVersion: v1
13+
kind: ResourceQuota
14+
metadata:
15+
name: example
16+
spec:
17+
hard:
18+
limits.cpu: '10'
19+
`),
20+
expected: true,
21+
},
22+
"k8s resource, cluster scoped": {
23+
input: []byte(`
24+
apiVersion: rbac.authorization.k8s.io/v1
25+
kind: ClusterRoleBinding
26+
metadata:
27+
name: example
28+
subjects:
29+
- kind: ServiceAccount
30+
name: example
31+
apiGroup: rbac.authorization.k8s.io
32+
`),
33+
expected: false,
34+
},
35+
"custom resource, namespace set": {
36+
input: []byte(`
37+
apiVersion: kpt-test
38+
kind: KptTestResource
39+
metadata:
40+
name: example
41+
namespace: example
42+
`),
43+
expected: true,
44+
},
45+
"custom resource, namespace unset": {
46+
input: []byte(`
47+
apiVersion: kpt-test
48+
kind: KptTestResource
49+
metadata:
50+
name: example
51+
`),
52+
expected: false,
53+
},
54+
}
55+
for description, data := range testdata {
56+
o, _ := ParseKubeObject(data.input)
57+
if o.IsNamespaceScoped() != data.expected {
58+
t.Errorf("%v failed, resource namespace scope: got %v, want %v", description, o.IsNamespaceScoped(), data.expected)
59+
}
60+
}
61+
}

0 commit comments

Comments
 (0)