Skip to content

Commit cb388c4

Browse files
authored
Merge pull request crossplane#6976 from adamwg/awg/no-more-input-crds
pkg: Stop installing function input CRDs and ignore disallowed kinds
2 parents f0d36fb + 36f59be commit cb388c4

6 files changed

Lines changed: 425 additions & 22 deletions

File tree

internal/controller/pkg/revision/establisher.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package revision
1919
import (
2020
"context"
2121
"fmt"
22+
"slices"
2223
"strings"
2324

2425
"golang.org/x/sync/errgroup"
@@ -29,6 +30,7 @@ import (
2930
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3031
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3132
"k8s.io/apimachinery/pkg/runtime"
33+
"k8s.io/apimachinery/pkg/runtime/schema"
3234
"k8s.io/apimachinery/pkg/types"
3335
"k8s.io/utils/ptr"
3436
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -579,3 +581,36 @@ func GetPackageOwnerReference(rev resource.Object) (metav1.OwnerReference, bool)
579581

580582
return metav1.OwnerReference{}, false
581583
}
584+
585+
// FilteringEstablisher wraps another establisher, but filters the objects that
586+
// get passed to it so that only certain kinds are allowed.
587+
type FilteringEstablisher struct {
588+
wrap Establisher
589+
gks []schema.GroupKind
590+
}
591+
592+
// NewFilteringEstablisher creates a new FilteringEstablisher.
593+
func NewFilteringEstablisher(wrap Establisher, gks ...schema.GroupKind) *FilteringEstablisher {
594+
return &FilteringEstablisher{
595+
wrap: wrap,
596+
gks: gks,
597+
}
598+
}
599+
600+
// Establish filters objects, then uses the wrapped establisher to establish
601+
// them.
602+
func (e *FilteringEstablisher) Establish(ctx context.Context, objects []runtime.Object, parent v1.PackageRevision, control bool) ([]xpv1.TypedReference, error) {
603+
filtered := make([]runtime.Object, 0, len(objects))
604+
for _, obj := range objects {
605+
if slices.Contains(e.gks, obj.GetObjectKind().GroupVersionKind().GroupKind()) {
606+
filtered = append(filtered, obj)
607+
}
608+
}
609+
610+
return e.wrap.Establish(ctx, filtered, parent, control)
611+
}
612+
613+
// ReleaseObjects uses the wrapped establisher to release objects.
614+
func (e *FilteringEstablisher) ReleaseObjects(ctx context.Context, parent v1.PackageRevision) error {
615+
return e.wrap.ReleaseObjects(ctx, parent)
616+
}

internal/controller/pkg/revision/establisher_test.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,3 +1230,155 @@ func newAPIEstablisher(client client.Client) *APIEstablisher {
12301230
MaxConcurrentPackageEstablishers: 10, // Use the current default
12311231
}
12321232
}
1233+
1234+
func TestFilteringEstablisherEstablish(t *testing.T) {
1235+
errBoom := errors.New("boom")
1236+
1237+
crd := &extv1.CustomResourceDefinition{
1238+
TypeMeta: metav1.TypeMeta{
1239+
APIVersion: extv1.SchemeGroupVersion.String(),
1240+
Kind: "CustomResourceDefinition",
1241+
},
1242+
ObjectMeta: metav1.ObjectMeta{
1243+
Name: "test-crd",
1244+
},
1245+
}
1246+
1247+
sa := &corev1.ServiceAccount{
1248+
TypeMeta: metav1.TypeMeta{
1249+
APIVersion: corev1.SchemeGroupVersion.String(),
1250+
Kind: "ServiceAccount",
1251+
},
1252+
ObjectMeta: metav1.ObjectMeta{
1253+
Name: "test-sa",
1254+
},
1255+
}
1256+
1257+
type args struct {
1258+
wrap Establisher
1259+
gks []schema.GroupKind
1260+
objs []runtime.Object
1261+
}
1262+
1263+
type want struct {
1264+
refs []xpv1.TypedReference
1265+
err error
1266+
}
1267+
1268+
cases := map[string]struct {
1269+
reason string
1270+
args args
1271+
want want
1272+
}{
1273+
"FilterPartialMatch": {
1274+
reason: "Should only pass objects matching the filter to the wrapped establisher",
1275+
args: args{
1276+
wrap: &MockEstablisher{
1277+
MockEstablish: func(_ context.Context, objects []runtime.Object, _ v1.PackageRevision, _ bool) ([]xpv1.TypedReference, error) {
1278+
if diff := cmp.Diff([]runtime.Object{crd}, objects); diff != "" {
1279+
t.Errorf("\n%s\nMockEstablish(...): -want error, +got error:\n%s", "incorrect objects passed to wrapped establisher", diff)
1280+
return nil, errBoom
1281+
}
1282+
1283+
return []xpv1.TypedReference{{Name: "test-crd"}}, nil
1284+
},
1285+
},
1286+
gks: []schema.GroupKind{crd.GroupVersionKind().GroupKind()},
1287+
objs: []runtime.Object{crd, sa},
1288+
},
1289+
want: want{
1290+
refs: []xpv1.TypedReference{{Name: "test-crd"}},
1291+
},
1292+
},
1293+
"FilterFullMatch": {
1294+
reason: "Should pass all objects matching any of the filters to the wrapped establisher",
1295+
args: args{
1296+
wrap: &MockEstablisher{
1297+
MockEstablish: func(_ context.Context, objects []runtime.Object, _ v1.PackageRevision, _ bool) ([]xpv1.TypedReference, error) {
1298+
if diff := cmp.Diff([]runtime.Object{crd, sa}, objects); diff != "" {
1299+
t.Errorf("\n%s\nMockEstablish(...): -want error, +got error:\n%s", "incorrect objects passed to wrapped establisher", diff)
1300+
return nil, errBoom
1301+
}
1302+
1303+
return []xpv1.TypedReference{{Name: "test-crd"}, {Name: "test-sa"}}, nil
1304+
},
1305+
},
1306+
gks: []schema.GroupKind{crd.GroupVersionKind().GroupKind(), sa.GroupVersionKind().GroupKind()},
1307+
objs: []runtime.Object{crd, sa},
1308+
},
1309+
want: want{
1310+
refs: []xpv1.TypedReference{{Name: "test-crd"}, {Name: "test-sa"}},
1311+
},
1312+
},
1313+
"FilterNoMatches": {
1314+
reason: "Should pass no objects to the wrapped establisher if none match the filter",
1315+
args: args{
1316+
wrap: &MockEstablisher{
1317+
MockEstablish: func(_ context.Context, objects []runtime.Object, _ v1.PackageRevision, _ bool) ([]xpv1.TypedReference, error) {
1318+
if diff := cmp.Diff([]runtime.Object{}, objects); diff != "" {
1319+
t.Errorf("\n%s\nMockEstablish(...): -want error, +got error:\n%s", "incorrect objects passed to wrapped establisher", diff)
1320+
return nil, errBoom
1321+
}
1322+
1323+
return []xpv1.TypedReference{}, nil
1324+
},
1325+
},
1326+
gks: []schema.GroupKind{{Group: "example.com", Kind: "CustomKind"}},
1327+
objs: []runtime.Object{crd, sa},
1328+
},
1329+
want: want{
1330+
refs: []xpv1.TypedReference{},
1331+
},
1332+
},
1333+
"FilterEmpty": {
1334+
reason: "Should pass no objects to the wrapped establisher if empty filter is specified",
1335+
args: args{
1336+
wrap: &MockEstablisher{
1337+
MockEstablish: func(_ context.Context, objects []runtime.Object, _ v1.PackageRevision, _ bool) ([]xpv1.TypedReference, error) {
1338+
if diff := cmp.Diff([]runtime.Object{}, objects); diff != "" {
1339+
t.Errorf("\n%s\nMockEstablish(...): -want error, +got error:\n%s", "incorrect objects passed to wrapped establisher", diff)
1340+
return nil, errBoom
1341+
}
1342+
1343+
return []xpv1.TypedReference{}, nil
1344+
},
1345+
},
1346+
gks: []schema.GroupKind{},
1347+
objs: []runtime.Object{crd, sa},
1348+
},
1349+
want: want{
1350+
refs: []xpv1.TypedReference{},
1351+
},
1352+
},
1353+
"ErrorFromWrappedEstablisher": {
1354+
reason: "Should propagate errors from the wrapped establisher",
1355+
args: args{
1356+
wrap: &MockEstablisher{
1357+
MockEstablish: func(_ context.Context, _ []runtime.Object, _ v1.PackageRevision, _ bool) ([]xpv1.TypedReference, error) {
1358+
return nil, errBoom
1359+
},
1360+
},
1361+
gks: []schema.GroupKind{crd.GroupVersionKind().GroupKind()},
1362+
objs: []runtime.Object{crd},
1363+
},
1364+
want: want{
1365+
err: errBoom,
1366+
},
1367+
},
1368+
}
1369+
1370+
for name, tc := range cases {
1371+
t.Run(name, func(t *testing.T) {
1372+
est := NewFilteringEstablisher(tc.args.wrap, tc.args.gks...)
1373+
refs, err := est.Establish(context.Background(), tc.args.objs, &v1.ProviderRevision{}, true)
1374+
1375+
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
1376+
t.Errorf("\n%s\nest.Establish(...): -want error, +got error:\n%s", tc.reason, diff)
1377+
}
1378+
1379+
if diff := cmp.Diff(tc.want.refs, refs); diff != "" {
1380+
t.Errorf("\n%s\nest.Establish(...): -want, +got:\n%s", tc.reason, diff)
1381+
}
1382+
})
1383+
}
1384+
}

internal/controller/pkg/revision/reconciler.go

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@ import (
2525
"strings"
2626
"time"
2727

28+
admv1 "k8s.io/api/admissionregistration/v1"
2829
corev1 "k8s.io/api/core/v1"
30+
k8sextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2931
kerrors "k8s.io/apimachinery/pkg/api/errors"
32+
"k8s.io/apimachinery/pkg/runtime/schema"
3033
"k8s.io/client-go/kubernetes"
3134
ctrl "sigs.k8s.io/controller-runtime"
3235
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -43,6 +46,10 @@ import (
4346
"github.com/crossplane/crossplane-runtime/v2/pkg/parser"
4447
"github.com/crossplane/crossplane-runtime/v2/pkg/resource"
4548

49+
extv1 "github.com/crossplane/crossplane/v2/apis/apiextensions/v1"
50+
extv1alpha1 "github.com/crossplane/crossplane/v2/apis/apiextensions/v1alpha1"
51+
extv2 "github.com/crossplane/crossplane/v2/apis/apiextensions/v2"
52+
opsv1alpha1 "github.com/crossplane/crossplane/v2/apis/ops/v1alpha1"
4653
pkgmetav1 "github.com/crossplane/crossplane/v2/apis/pkg/meta/v1"
4754
v1 "github.com/crossplane/crossplane/v2/apis/pkg/v1"
4855
"github.com/crossplane/crossplane/v2/apis/pkg/v1beta1"
@@ -82,6 +89,7 @@ const (
8289
errInitParserBackend = "cannot initialize parser backend"
8390
errParsePackage = "cannot parse package contents"
8491
errLintPackage = "linting package contents failed"
92+
errValidatePackage = "validating package contents failed"
8593
errNotOneMeta = "cannot install package with multiple meta types"
8694
errIncompatible = "incompatible Crossplane version"
8795

@@ -108,6 +116,7 @@ const (
108116
reasonImageConfig event.Reason = "ImageConfigSelection"
109117
reasonParse event.Reason = "ParsePackage"
110118
reasonLint event.Reason = "LintPackage"
119+
reasonValidate event.Reason = "ValidatePackage"
111120
reasonDependencies event.Reason = "ResolveDependencies"
112121
reasonConvertCRD event.Reason = "ConvertCRDToMRD"
113122
reasonSync event.Reason = "SyncPackage"
@@ -204,6 +213,13 @@ func WithLinter(l parser.Linter) ReconcilerOption {
204213
}
205214
}
206215

216+
// WithValidator specifies how the Reconciler should validate a package.
217+
func WithValidator(v xpkg.Validator) ReconcilerOption {
218+
return func(r *Reconciler) {
219+
r.validator = v
220+
}
221+
}
222+
207223
// WithVersioner specifies how the Reconciler should fetch the current
208224
// Crossplane version.
209225
func WithVersioner(v version.Operations) ReconcilerOption {
@@ -243,6 +259,7 @@ type Reconciler struct {
243259
objects Establisher
244260
parser parser.Parser
245261
linter parser.Linter
262+
validator xpkg.Validator
246263
versioner version.Operations
247264
backend parser.Backend
248265
config xpkg.ConfigStore
@@ -289,15 +306,24 @@ func SetupProviderRevision(mgr ctrl.Manager, o controller.Options) error {
289306
Watches(&v1beta1.Lock{}, EnqueuePackageRevisionsForLock(mgr.GetClient(), &v1.ProviderRevisionList{}, log)).
290307
Watches(&v1beta1.ImageConfig{}, EnqueuePackageRevisionsForImageConfig(mgr.GetClient(), &v1.ProviderRevisionList{}, log))
291308

309+
est := NewFilteringEstablisher(
310+
NewAPIEstablisher(mgr.GetClient(), o.Namespace, o.MaxConcurrentPackageEstablishers),
311+
extv1alpha1.ManagedResourceDefinitionGroupVersionKind.GroupKind(),
312+
schema.GroupKind{Group: k8sextv1.SchemeGroupVersion.Group, Kind: "CustomResourceDefinition"},
313+
schema.GroupKind{Group: admv1.SchemeGroupVersion.Group, Kind: "ValidatingWebhookConfiguration"},
314+
schema.GroupKind{Group: admv1.SchemeGroupVersion.Group, Kind: "MutatingWebhookConfiguration"},
315+
)
316+
292317
r := NewReconciler(mgr,
293318
WithCache(o.Cache),
294319
WithDependencyManager(NewPackageDependencyManager(mgr.GetClient(), dag.NewMapDag, v1.ProviderGroupVersionKind, log)),
295-
WithEstablisher(NewAPIEstablisher(mgr.GetClient(), o.Namespace, o.MaxConcurrentPackageEstablishers)),
320+
WithEstablisher(est),
296321
WithNewPackageRevisionFn(nr),
297322
WithParser(parser.New(metaScheme, objScheme)),
298323
WithParserBackend(NewImageBackend(fetcher)),
299324
WithConfigStore(xpkg.NewImageConfigStore(mgr.GetClient(), o.Namespace)),
300325
WithLinter(xpkg.NewProviderLinter()),
326+
WithValidator(xpkg.NewProviderValidator()),
301327
WithLogger(log),
302328
WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name), o.EventFilterFunctions...)),
303329
WithNamespace(o.Namespace),
@@ -334,16 +360,27 @@ func SetupConfigurationRevision(mgr ctrl.Manager, o controller.Options) error {
334360
return errors.Wrap(err, errCannotBuildFetcher)
335361
}
336362

363+
est := NewFilteringEstablisher(
364+
NewAPIEstablisher(mgr.GetClient(), o.Namespace, o.MaxConcurrentPackageEstablishers),
365+
extv2.CompositeResourceDefinitionGroupVersionKind.GroupKind(),
366+
extv1.CompositionGroupVersionKind.GroupKind(),
367+
extv1alpha1.ManagedResourceActivationPolicyGroupVersionKind.GroupKind(),
368+
opsv1alpha1.OperationGroupVersionKind.GroupKind(),
369+
opsv1alpha1.CronOperationGroupVersionKind.GroupKind(),
370+
opsv1alpha1.WatchOperationGroupVersionKind.GroupKind(),
371+
)
372+
337373
log := o.Logger.WithValues("controller", name)
338374
r := NewReconciler(mgr,
339375
WithCache(o.Cache),
340376
WithDependencyManager(NewPackageDependencyManager(mgr.GetClient(), dag.NewMapDag, v1.ConfigurationGroupVersionKind, log)),
341377
WithNewPackageRevisionFn(nr),
342-
WithEstablisher(NewAPIEstablisher(mgr.GetClient(), o.Namespace, o.MaxConcurrentPackageEstablishers)),
378+
WithEstablisher(est),
343379
WithParser(parser.New(metaScheme, objScheme)),
344380
WithParserBackend(NewImageBackend(f)),
345381
WithConfigStore(xpkg.NewImageConfigStore(mgr.GetClient(), o.Namespace)),
346382
WithLinter(xpkg.NewConfigurationLinter()),
383+
WithValidator(xpkg.NewConfigurationValidator()),
347384
WithLogger(log),
348385
WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name), o.EventFilterFunctions...)),
349386
WithNamespace(o.Namespace),
@@ -393,15 +430,24 @@ func SetupFunctionRevision(mgr ctrl.Manager, o controller.Options) error {
393430
Watches(&v1beta1.Lock{}, EnqueuePackageRevisionsForLock(mgr.GetClient(), &v1.FunctionRevisionList{}, log)).
394431
Watches(&v1beta1.ImageConfig{}, EnqueuePackageRevisionsForImageConfig(mgr.GetClient(), &v1.FunctionRevisionList{}, log))
395432

433+
// The xpkg spec allows for CRDs to be included in function packages, but
434+
// states that they will not be installed. This means we shouldn't install
435+
// any objects from function packages. Create an empty filtering establisher
436+
// to filter them all out.
437+
est := NewFilteringEstablisher(
438+
NewAPIEstablisher(mgr.GetClient(), o.Namespace, o.MaxConcurrentPackageEstablishers),
439+
)
440+
396441
r := NewReconciler(mgr,
397442
WithCache(o.Cache),
398443
WithDependencyManager(NewPackageDependencyManager(mgr.GetClient(), dag.NewMapDag, v1.FunctionGroupVersionKind, log)),
399-
WithEstablisher(NewAPIEstablisher(mgr.GetClient(), o.Namespace, o.MaxConcurrentPackageEstablishers)),
444+
WithEstablisher(est),
400445
WithNewPackageRevisionFn(nr),
401446
WithParser(parser.New(metaScheme, objScheme)),
402447
WithParserBackend(NewImageBackend(fetcher)),
403448
WithConfigStore(xpkg.NewImageConfigStore(mgr.GetClient(), o.Namespace)),
404449
WithLinter(xpkg.NewFunctionLinter()),
450+
WithValidator(xpkg.NewFunctionValidator()),
405451
WithLogger(log),
406452
WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name), o.EventFilterFunctions...)),
407453
WithNamespace(o.Namespace),
@@ -784,22 +830,29 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
784830
return reconcile.Result{}, err
785831
}
786832

787-
// Lint package using package-specific linter.
788-
if err := r.linter.Lint(pkg); err != nil {
789-
err = errors.Wrap(err, errLintPackage)
833+
// Validate the package using a package-specific validator. If validation
834+
// fails, we won't try to install the package.
835+
if err := r.validator.Lint(pkg); err != nil {
836+
err = errors.Wrap(err, errValidatePackage)
790837
status.MarkConditions(v1.RevisionUnhealthy().WithMessage(err.Error()))
791838

792839
_ = r.client.Status().Update(ctx, pr)
793840

794-
r.record.Event(pr, event.Warning(reasonLint, err))
841+
r.record.Event(pr, event.Warning(reasonValidate, err))
795842

796-
// NOTE(hasheddan): a failed lint typically will require manual
797-
// intervention, but on the off chance that we read pod logs
798-
// early, which caused a linting failure, we will requeue by
799-
// returning an error.
800843
return reconcile.Result{}, err
801844
}
802845

846+
// Lint package using package-specific linter. We can proceed with
847+
// installation even there are lint errors; we just record them in an event
848+
// for the user's information since they may cause unexpected behavior.
849+
if err := r.linter.Lint(pkg); err != nil {
850+
err = errors.Wrap(err, errLintPackage)
851+
r.record.Event(pr, event.Warning(reasonLint, err))
852+
// TODO(adamwg): Should we also record lint errors in the status for
853+
// posterity? Events are ephemeral.
854+
}
855+
803856
// NOTE(hasheddan): the linter should check this property already, but
804857
// if a consumer forgets to pass an option to guarantee one meta object,
805858
// we check here to avoid a potential panic on 0 index below.

0 commit comments

Comments
 (0)