Skip to content

Commit eae6b41

Browse files
authored
Merge pull request crossplane#6054 from negz/dependent
Have the resolver (`Lock`) controller watch package revisions
2 parents e4d72e5 + c7b6640 commit eae6b41

File tree

7 files changed

+266
-23
lines changed

7 files changed

+266
-23
lines changed

Earthfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ go-generate:
141141
&& mv /tmp/patched.yaml cluster/crds/pkg.crossplane.io_deploymentruntimeconfigs.yaml
142142
SAVE ARTIFACT apis/ AS LOCAL apis
143143
SAVE ARTIFACT cluster/crds AS LOCAL cluster/crds
144+
SAVE ARTIFACT cluster/meta AS LOCAL cluster/meta
144145

145146
# go-build builds Crossplane binaries for your native OS and architecture.
146147
go-build:

cluster/meta/meta.pkg.crossplane.io_configurations.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
33
kind: CustomResourceDefinition
44
metadata:
55
annotations:
6-
controller-gen.kubebuilder.io/version: v0.14.0
6+
controller-gen.kubebuilder.io/version: v0.16.5
77
name: configurations.meta.pkg.crossplane.io
88
spec:
99
group: meta.pkg.crossplane.io

cluster/meta/meta.pkg.crossplane.io_functions.yaml

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
33
kind: CustomResourceDefinition
44
metadata:
55
annotations:
6-
controller-gen.kubebuilder.io/version: v0.14.0
6+
controller-gen.kubebuilder.io/version: v0.16.5
77
name: functions.meta.pkg.crossplane.io
88
spec:
99
group: meta.pkg.crossplane.io
@@ -14,7 +14,7 @@ spec:
1414
singular: function
1515
scope: Namespaced
1616
versions:
17-
- name: v1beta1
17+
- name: v1
1818
schema:
1919
openAPIV3Schema:
2020
description: A Function is the description of a Crossplane Function package.
@@ -83,3 +83,72 @@ spec:
8383
type: object
8484
served: true
8585
storage: true
86+
- name: v1beta1
87+
schema:
88+
openAPIV3Schema:
89+
description: A Function is the description of a Crossplane Function package.
90+
properties:
91+
apiVersion:
92+
description: |-
93+
APIVersion defines the versioned schema of this representation of an object.
94+
Servers should convert recognized schemas to the latest internal value, and
95+
may reject unrecognized values.
96+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
97+
type: string
98+
kind:
99+
description: |-
100+
Kind is a string value representing the REST resource this object represents.
101+
Servers may infer this from the endpoint the client submits requests to.
102+
Cannot be updated.
103+
In CamelCase.
104+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
105+
type: string
106+
metadata:
107+
type: object
108+
spec:
109+
description: FunctionSpec specifies the configuration of a Function.
110+
properties:
111+
crossplane:
112+
description: Semantic version constraints of Crossplane that package
113+
is compatible with.
114+
properties:
115+
version:
116+
description: Semantic version constraints of Crossplane that package
117+
is compatible with.
118+
type: string
119+
required:
120+
- version
121+
type: object
122+
dependsOn:
123+
description: Dependencies on other packages.
124+
items:
125+
description: Dependency is a dependency on another package. One
126+
of Provider or Configuration may be supplied.
127+
properties:
128+
configuration:
129+
description: Configuration is the name of a Configuration package
130+
image.
131+
type: string
132+
function:
133+
description: Function is the name of a Function package image.
134+
type: string
135+
provider:
136+
description: Provider is the name of a Provider package image.
137+
type: string
138+
version:
139+
description: Version is the semantic version constraints of
140+
the dependency image.
141+
type: string
142+
required:
143+
- version
144+
type: object
145+
type: array
146+
image:
147+
description: Image is the packaged Function image.
148+
type: string
149+
type: object
150+
required:
151+
- spec
152+
type: object
153+
served: true
154+
storage: false

cluster/meta/meta.pkg.crossplane.io_providers.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
33
kind: CustomResourceDefinition
44
metadata:
55
annotations:
6-
controller-gen.kubebuilder.io/version: v0.14.0
6+
controller-gen.kubebuilder.io/version: v0.16.5
77
name: providers.meta.pkg.crossplane.io
88
spec:
99
group: meta.pkg.crossplane.io

internal/controller/pkg/resolver/reconciler.go

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -173,25 +173,16 @@ func Setup(mgr ctrl.Manager, o controller.Options) error {
173173
return ctrl.NewControllerManagedBy(mgr).
174174
Named(name).
175175
For(&v1beta1.Lock{}).
176-
Owns(&v1.ConfigurationRevision{}).
177-
Owns(&v1.ProviderRevision{}).
178-
Watches(&v1beta1.ImageConfig{}, handler.EnqueueRequestsFromMapFunc(func(_ context.Context, o client.Object) []reconcile.Request {
179-
ic, ok := o.(*v1beta1.ImageConfig)
180-
if !ok {
181-
return nil
182-
}
183-
// We only care about ImageConfigs that have a pull secret.
184-
if ic.Spec.Registry == nil || ic.Spec.Registry.Authentication == nil || ic.Spec.Registry.Authentication.PullSecretRef.Name == "" {
185-
return nil
186-
}
187-
// Ideally we should enqueue only if the ImageConfig applies to a
188-
// package in the Lock which would require getting/parsing the Lock
189-
// and checking the source of each package against the prefixes in
190-
// the ImageConfig. However, this is a bit more complex than needed,
191-
// and we don't expect to have many ImageConfigs so we just enqueue
192-
// for all ImageConfigs with a pull secret.
193-
return []reconcile.Request{{NamespacedName: client.ObjectKey{Name: lockName}}}
194-
})).
176+
Watches(&v1.ConfigurationRevision{}, handler.EnqueueRequestsFromMapFunc(ForName(lockName))).
177+
Watches(&v1.ProviderRevision{}, handler.EnqueueRequestsFromMapFunc(ForName(lockName))).
178+
Watches(&v1.FunctionRevision{}, handler.EnqueueRequestsFromMapFunc(ForName(lockName))).
179+
// Ideally we should enqueue only if the ImageConfig applies to a
180+
// package in the Lock which would require getting/parsing the Lock and
181+
// checking the source of each package against the prefixes in the
182+
// ImageConfig. However, this is a bit more complex than needed, and we
183+
// don't expect to have many ImageConfigs so we just enqueue for all
184+
// ImageConfigs with a pull secret.
185+
Watches(&v1beta1.ImageConfig{}, handler.EnqueueRequestsFromMapFunc(ForName(lockName, HasPullSecret()))).
195186
WithOptions(o.ForControllerRuntime()).
196187
Complete(ratelimiter.NewReconciler(name, errors.WithSilentRequeueOnConflict(NewReconciler(mgr, opts...)), o.GlobalRateLimiter))
197188
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
Copyright 2024 The Crossplane Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package resolver
18+
19+
import (
20+
"context"
21+
22+
"sigs.k8s.io/controller-runtime/pkg/client"
23+
"sigs.k8s.io/controller-runtime/pkg/handler"
24+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
25+
26+
"github.com/crossplane/crossplane/apis/pkg/v1beta1"
27+
)
28+
29+
// A FilterFn returns true if the supplied object should be filtered.
30+
type FilterFn func(o client.Object) bool
31+
32+
// HasPullSecret returns a FilterFn that filters any object that either isn't an
33+
// ImageConfig, or is an ImageConfig that doesn't reference a pull secret.
34+
func HasPullSecret() FilterFn {
35+
return func(o client.Object) bool {
36+
ic, ok := o.(*v1beta1.ImageConfig)
37+
if !ok {
38+
// Not an ImageConfig - filter it.
39+
return true
40+
}
41+
// Filter ImageConfigs that don't have a pull secret.
42+
return ic.Spec.Registry == nil || ic.Spec.Registry.Authentication == nil || ic.Spec.Registry.Authentication.PullSecretRef.Name == ""
43+
}
44+
}
45+
46+
// ForName enqueues a request for the named object. It only enqueues a request
47+
// if all supplied filter functions return false.
48+
func ForName(name string, fns ...FilterFn) handler.MapFunc {
49+
return func(_ context.Context, o client.Object) []reconcile.Request {
50+
for _, filter := range fns {
51+
if filter(o) {
52+
return nil
53+
}
54+
}
55+
return []reconcile.Request{{NamespacedName: client.ObjectKey{Name: name}}}
56+
}
57+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
Copyright 2024 The Crossplane Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package resolver
18+
19+
import (
20+
"context"
21+
"testing"
22+
23+
"github.com/google/go-cmp/cmp"
24+
v1 "k8s.io/api/core/v1"
25+
"sigs.k8s.io/controller-runtime/pkg/client"
26+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
27+
28+
"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
29+
30+
"github.com/crossplane/crossplane/apis/pkg/v1beta1"
31+
)
32+
33+
func TestHasPullSecret(t *testing.T) {
34+
cases := map[string]struct {
35+
reason string
36+
o client.Object
37+
want bool
38+
}{
39+
"NotAnImageConfig": {
40+
reason: "Objects that aren't an ImageConfig should be filtered.",
41+
o: &fake.Object{},
42+
want: true,
43+
},
44+
"ImageConfigWithoutPullSecret": {
45+
reason: "An ImageConfig without a pull secret should be filtered.",
46+
o: &v1beta1.ImageConfig{},
47+
want: true,
48+
},
49+
"ImageConfigWithPullSecret": {
50+
reason: "An ImageConfig with a pull secret shouldn't be filtered.",
51+
o: &v1beta1.ImageConfig{
52+
Spec: v1beta1.ImageConfigSpec{
53+
Registry: &v1beta1.RegistryConfig{
54+
Authentication: &v1beta1.RegistryAuthentication{
55+
PullSecretRef: v1.LocalObjectReference{
56+
Name: "supersecret",
57+
},
58+
},
59+
},
60+
},
61+
},
62+
want: false,
63+
},
64+
}
65+
66+
for name, tc := range cases {
67+
t.Run(name, func(t *testing.T) {
68+
got := HasPullSecret()(tc.o)
69+
70+
if diff := cmp.Diff(tc.want, got); diff != "" {
71+
t.Errorf("\n%s\nHasPullsecret(): -want, +got:\n%s", tc.reason, diff)
72+
}
73+
})
74+
}
75+
}
76+
77+
func TestForName(t *testing.T) {
78+
type args struct {
79+
name string
80+
fns []FilterFn
81+
}
82+
cases := map[string]struct {
83+
reason string
84+
args args
85+
want []reconcile.Request
86+
}{
87+
"NoFilters": {
88+
reason: "A request should be enqueued for the named object if there are no filter functions.",
89+
args: args{
90+
name: "cool-object",
91+
},
92+
want: []reconcile.Request{{NamespacedName: client.ObjectKey{Name: "cool-object"}}},
93+
},
94+
"AllFiltersReturnFalse": {
95+
reason: "A request should be enqueued for the named object if all filter functions return false.",
96+
args: args{
97+
name: "cool-object",
98+
fns: []FilterFn{func(_ client.Object) bool { return false }},
99+
},
100+
want: []reconcile.Request{{NamespacedName: client.ObjectKey{Name: "cool-object"}}},
101+
},
102+
"OneFilterReturnsTrue": {
103+
reason: "A request shouldn't be enqueued for the named object if any filter functions return true.",
104+
args: args{
105+
name: "cool-object",
106+
fns: []FilterFn{
107+
func(_ client.Object) bool { return false },
108+
func(_ client.Object) bool { return true },
109+
func(_ client.Object) bool { return false },
110+
},
111+
},
112+
want: nil,
113+
},
114+
}
115+
116+
for name, tc := range cases {
117+
t.Run(name, func(t *testing.T) {
118+
got := ForName(tc.args.name, tc.args.fns...)(context.Background(), &fake.Object{})
119+
120+
if diff := cmp.Diff(tc.want, got); diff != "" {
121+
t.Errorf("\n%s\nForName(): -want, +got:\n%s", tc.reason, diff)
122+
}
123+
})
124+
}
125+
}

0 commit comments

Comments
 (0)