-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathcatalog_observe_test.go
More file actions
178 lines (153 loc) · 7.18 KB
/
Copy pathcatalog_observe_test.go
File metadata and controls
178 lines (153 loc) · 7.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// SPDX-License-Identifier: Apache-2.0
package watch
import (
"context"
"testing"
"github.com/go-logr/logr"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/ConfigButler/gitops-reverser/internal/types"
"github.com/ConfigButler/gitops-reverser/internal/typeset"
)
// followableDiscovery serves a realistic mix the funnel must judge: a followable
// built-in (deployments, with a /scale subresource), a followable CRD, a
// policy-denied built-in (pods), and a built-in missing the watch verb (nodes).
func followableDiscovery() staticCatalogDiscovery {
full := metav1.Verbs{"get", "list", "watch", "patch", "create", "delete"}
// getList lacks watch, so a type with these verbs cannot be followed.
getList := metav1.Verbs{"get", "list"}
return staticCatalogDiscovery{
groups: []*metav1.APIGroup{
testAPIGroup("", "v1"),
testAPIGroup("apps", "v1"),
testAPIGroup("shop.example.com", "v1alpha1"),
},
resources: []*metav1.APIResourceList{
{
GroupVersion: "v1",
APIResources: []metav1.APIResource{
{Name: "configmaps", Kind: "ConfigMap", Namespaced: true, Verbs: full},
{Name: "secrets", Kind: "Secret", Namespaced: true, Verbs: full},
{Name: "pods", Kind: "Pod", Namespaced: true, Verbs: full},
{Name: "nodes", Kind: "Node", Verbs: getList},
},
},
{
GroupVersion: "apps/v1",
APIResources: []metav1.APIResource{
{Name: "deployments", Kind: "Deployment", Namespaced: true, Verbs: full},
{Name: "deployments/scale", Kind: "Scale", Namespaced: true, Verbs: metav1.Verbs{"get", "patch"}},
},
},
{
GroupVersion: "shop.example.com/v1alpha1",
APIResources: []metav1.APIResource{
{Name: "icecreamorders", Kind: "IceCreamOrder", Namespaced: true, Verbs: full},
},
},
},
}
}
func recordByGVR(t *testing.T, r *typeset.Registry, group, version, resource string) typeset.TypeRecord {
t.Helper()
rec, ok := r.ByGVR(schema.GroupVersionResource{Group: group, Version: version, Resource: resource})
require.True(t, ok, "registry should know %s/%s/%s", group, version, resource)
return rec
}
func TestObservations_FollowabilityFromCatalog(t *testing.T) {
catalog := NewAPIResourceCatalog()
_, err := catalog.Refresh(followableDiscovery())
require.NoError(t, err)
reg := registryFromCatalog(t, catalog, types.SensitiveResourcePolicy{})
// Deployment: followable built-in with a usable scale binding folded in.
dep := recordByGVR(t, reg, "apps", "v1", "deployments")
assert.True(t, dep.Followable(), "deployment should be followable: %s", dep.Followability.Summary)
assert.Equal(t, typeset.OriginBuiltin, dep.Origin.Kind)
assert.True(t, dep.Subresources.Scale.Enabled, "deployment exposes /scale")
assert.True(t, dep.Subresources.Scale.Usable, "built-in scale binding is usable")
assert.Equal(t, ".spec.replicas", dep.Subresources.Scale.SpecReplicasPath)
// CRD: followable, classified crd by group shape.
order := recordByGVR(t, reg, "shop.example.com", "v1alpha1", "icecreamorders")
assert.True(t, order.Followable())
assert.Equal(t, typeset.OriginCRD, order.Origin.Kind)
// Pod: served and fully verbed but denied by default watch policy.
pod := recordByGVR(t, reg, "", "v1", "pods")
assert.False(t, pod.Followable())
policy, _ := pod.Followability.Check(typeset.RequirementPolicy)
assert.Equal(t, typeset.ReasonDeniedByPolicy, policy.Reason)
// Node: built-in lacking watch -> refused for the missing verb (cannot follow it).
node := recordByGVR(t, reg, "", "v1", "nodes")
assert.False(t, node.Followable())
verbs, _ := node.Followability.Check(typeset.RequirementVerbs)
assert.Equal(t, typeset.ReasonMissingVerb, verbs.Reason)
assert.Equal(t, "watch", verbs.Detail)
// Secret is sensitive but supported, so it stays followable.
secret := recordByGVR(t, reg, "", "v1", "secrets")
assert.True(t, secret.Sensitive)
assert.True(t, secret.Followable())
// The scale subresource itself is folded into the parent, never its own record.
_, ok := reg.ByGVR(schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments/scale"})
assert.False(t, ok, "subresources must not enter the registry as their own types")
}
func TestObservations_AppliesConfiguredSensitivePolicy(t *testing.T) {
catalog := NewAPIResourceCatalog()
_, err := catalog.Refresh(followableDiscovery())
require.NoError(t, err)
// The operator additionally marks configmaps sensitive; core secrets stay sensitive
// by default. The registry record must reflect the configured policy, not just the
// built-in core-secret rule.
policy, err := types.ParseSensitiveResourcePolicy("configmaps")
require.NoError(t, err)
reg := registryFromCatalog(t, catalog, policy)
cm := recordByGVR(t, reg, "", "v1", "configmaps")
assert.True(t, cm.Sensitive, "an operator-configured sensitive type must be marked sensitive")
secret := recordByGVR(t, reg, "", "v1", "secrets")
assert.True(t, secret.Sensitive, "core secrets stay sensitive")
dep := recordByGVR(t, reg, "apps", "v1", "deployments")
assert.False(t, dep.Sensitive, "an unlisted type is not sensitive")
}
func TestObservations_AmbiguousGVKMarkedNonUnique(t *testing.T) {
full := metav1.Verbs{"get", "list", "watch", "patch"}
catalog := NewAPIResourceCatalog()
_, err := catalog.Refresh(staticCatalogDiscovery{
groups: []*metav1.APIGroup{testAPIGroup("shop.example.com", "v1")},
resources: []*metav1.APIResourceList{{
GroupVersion: "shop.example.com/v1",
APIResources: []metav1.APIResource{
// Two resources serving the same kind: a pathological cluster.
{Name: "widgets", Kind: "Widget", Namespaced: true, Verbs: full},
{Name: "widgetz", Kind: "Widget", Namespaced: true, Verbs: full},
},
}},
})
require.NoError(t, err)
reg := registryFromCatalog(t, catalog, types.SensitiveResourcePolicy{})
rec, ok := reg.ByGVK(schema.GroupVersionKind{Group: "shop.example.com", Version: "v1", Kind: "Widget"})
require.True(t, ok)
assert.False(t, rec.Followable(), "an ambiguous kind must be refused")
id, _ := rec.Followability.Check(typeset.RequirementIdentity)
assert.Equal(t, typeset.ReasonGVKNotUnique, id.Reason)
assert.Equal(t, "widgets, widgetz", id.Detail)
}
func TestManager_RefreshPopulatesTypeRegistry(t *testing.T) {
m := &Manager{Log: logr.Discard(), discoveryClient: func() (apiResourceDiscovery, error) {
return followableDiscovery(), nil
}}
require.NoError(t, m.refreshAPIResourceCatalog(context.Background()))
followable := m.FollowableTypeRecords()
all := m.TypeRecords()
assert.NotEmpty(t, followable, "deployments/configmaps/secrets/icecreamorders should be followable")
assert.Greater(t, len(all), len(followable), "All() must also include refused types (pods, nodes)")
// The registry tracks the catalog generation.
assert.Equal(t, m.apiResourceCatalog().Generation(), m.typeRegistryInstance().Generation())
gvks := map[string]bool{}
for _, rec := range followable {
gvks[rec.Identity.GVK.Kind] = true
}
assert.True(t, gvks["Deployment"], "Deployment should be followable")
assert.True(t, gvks["IceCreamOrder"], "the CRD should be followable")
assert.False(t, gvks["Pod"], "Pod is denied by policy")
assert.False(t, gvks["Node"], "Node is missing the watch verb")
}