Skip to content

Commit 2203fbf

Browse files
committed
Boxcutter Phases
Defines a set of phases which facilitate a smoother installation vs applying every resource in the bundle all at once. Signed-off-by: Daniel Franz <[email protected]>
1 parent ae9d77c commit 2203fbf

File tree

4 files changed

+436
-13
lines changed

4 files changed

+436
-13
lines changed

internal/operator-controller/applier/boxcutter.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,7 @@ func (r *SimpleRevisionGenerator) GenerateRevision(bundleFS fs.FS, ext *ocv1.Clu
8989
},
9090
},
9191
Spec: ocv1.ClusterExtensionRevisionSpec{
92-
Phases: []ocv1.ClusterExtensionRevisionPhase{
93-
{
94-
Name: "everything",
95-
Objects: objs,
96-
},
97-
},
92+
Phases: PhaseSort(objs),
9893
},
9994
}, nil
10095
}

internal/operator-controller/applier/boxcutter_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ func Test_SimpleRevisionGenerator_Success(t *testing.T) {
122122
t.Log("by checking the rendered objects are present in the correct phases")
123123
require.Equal(t, []ocv1.ClusterExtensionRevisionPhase{
124124
{
125-
Name: "everything",
125+
Name: string(applier.PhaseDeploy),
126126
Objects: []ocv1.ClusterExtensionRevisionObject{
127127
{
128128
Object: unstructured.Unstructured{
@@ -268,7 +268,7 @@ func TestBoxcutter_Apply(t *testing.T) {
268268
UID: "test-uid",
269269
},
270270
}
271-
defaultDesiredHash := "2a3d3548913494df7d4cbaef51cb6c36f6f67399cbfe2dc6a3cc49e4db0083ae"
271+
defaultDesiredHash := "faaeb52a1cb7c968c96278bc1cd804e50d3ae9faae08807c9279a5e569933ea0"
272272
defaultDesiredRevision := &ocv1.ClusterExtensionRevision{
273273
ObjectMeta: metav1.ObjectMeta{
274274
Name: "test-ext-1",
@@ -284,7 +284,7 @@ func TestBoxcutter_Apply(t *testing.T) {
284284
Revision: 1,
285285
Phases: []ocv1.ClusterExtensionRevisionPhase{
286286
{
287-
Name: "everything",
287+
Name: string(applier.PhaseDeploy),
288288
Objects: []ocv1.ClusterExtensionRevisionObject{
289289
{
290290
Object: unstructured.Unstructured{
@@ -324,7 +324,7 @@ func TestBoxcutter_Apply(t *testing.T) {
324324
Spec: ocv1.ClusterExtensionRevisionSpec{
325325
Phases: []ocv1.ClusterExtensionRevisionPhase{
326326
{
327-
Name: "everything",
327+
Name: string(applier.PhaseDeploy),
328328
Objects: []ocv1.ClusterExtensionRevisionObject{
329329
{
330330
Object: unstructured.Unstructured{
@@ -373,7 +373,7 @@ func TestBoxcutter_Apply(t *testing.T) {
373373
Spec: ocv1.ClusterExtensionRevisionSpec{
374374
Phases: []ocv1.ClusterExtensionRevisionPhase{
375375
{
376-
Name: "everything",
376+
Name: string(applier.PhaseDeploy),
377377
Objects: []ocv1.ClusterExtensionRevisionObject{
378378
{
379379
Object: unstructured.Unstructured{
@@ -419,7 +419,7 @@ func TestBoxcutter_Apply(t *testing.T) {
419419
Spec: ocv1.ClusterExtensionRevisionSpec{
420420
Phases: []ocv1.ClusterExtensionRevisionPhase{
421421
{
422-
Name: "everything",
422+
Name: string(applier.PhaseDeploy),
423423
Objects: []ocv1.ClusterExtensionRevisionObject{
424424
{
425425
Object: unstructured.Unstructured{
@@ -460,7 +460,7 @@ func TestBoxcutter_Apply(t *testing.T) {
460460

461461
assert.Equal(t, "test-ext-2", newRev.Name)
462462
assert.Equal(t, int64(2), newRev.Spec.Revision)
463-
assert.Equal(t, "bc1c7457a476193460747e8223fff9b492f0a2f60057831fb55a88ec8c2387b2", newRev.Annotations[applier.RevisionHashAnnotation])
463+
assert.Equal(t, "ec8213d4061a75b55cd67a009d9cdeb1bdd6f503d4b3bb7b6cfea3a5233aad43", newRev.Annotations[applier.RevisionHashAnnotation])
464464
require.Len(t, newRev.Spec.Previous, 1)
465465
assert.Equal(t, "test-ext-1", newRev.Spec.Previous[0].Name)
466466
assert.Equal(t, types.UID("rev-uid-1"), newRev.Spec.Previous[0].UID)
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package applier
2+
3+
import (
4+
"k8s.io/apimachinery/pkg/runtime/schema"
5+
6+
ocv1 "github.com/operator-framework/operator-controller/api/v1"
7+
)
8+
9+
// The following, with modifications, is taken from:
10+
// https://github.com/package-operator/package-operator/blob/v1.18.2/internal/packages/internal/packagekickstart/presets/phases.go
11+
//
12+
// Determines a phase using the objects Group Kind from a list of presets.
13+
// Defaults to the `deploy` phase if no preset was found. Runtimes that
14+
// depend on a custom resource to start i.e. certmanager's Certificate
15+
// will require this.
16+
func determinePhase(gk schema.GroupKind) Phase {
17+
phase, ok := gkPhaseMap[gk]
18+
if !ok {
19+
return PhaseDeploy
20+
}
21+
return phase
22+
}
23+
24+
// Phase represents a well-known phase.
25+
type Phase string
26+
27+
const (
28+
PhaseNamespaces Phase = "namespaces"
29+
PhasePolicies Phase = "policies"
30+
PhaseRBAC Phase = "rbac"
31+
PhaseCRDs Phase = "crds"
32+
PhaseStorage Phase = "storage"
33+
PhaseDeploy Phase = "deploy"
34+
PhasePublish Phase = "publish"
35+
)
36+
37+
// Well known phases ordered.
38+
var defaultPhaseOrder = []Phase{
39+
PhaseNamespaces,
40+
PhasePolicies,
41+
PhaseRBAC,
42+
PhaseCRDs,
43+
PhaseStorage,
44+
PhaseDeploy,
45+
PhasePublish,
46+
}
47+
48+
var (
49+
// This will be populated from `phaseGKMap` in an init func!
50+
gkPhaseMap = map[schema.GroupKind]Phase{}
51+
phaseGKMap = map[Phase][]schema.GroupKind{
52+
PhaseNamespaces: {
53+
{Kind: "Namespace"},
54+
},
55+
56+
PhasePolicies: {
57+
{Kind: "ResourceQuota"},
58+
{Kind: "LimitRange"},
59+
{Kind: "PriorityClass", Group: "scheduling.k8s.io"},
60+
{Kind: "NetworkPolicy", Group: "networking.k8s.io"},
61+
{Kind: "HorizontalPodAutoscaler", Group: "autoscaling"},
62+
{Kind: "PodDisruptionBudget", Group: "policy"},
63+
},
64+
65+
PhaseRBAC: {
66+
{Kind: "ServiceAccount"},
67+
{Kind: "Role", Group: "rbac.authorization.k8s.io"},
68+
{Kind: "RoleBinding", Group: "rbac.authorization.k8s.io"},
69+
{Kind: "ClusterRole", Group: "rbac.authorization.k8s.io"},
70+
{Kind: "ClusterRoleBinding", Group: "rbac.authorization.k8s.io"},
71+
},
72+
73+
PhaseCRDs: {
74+
{Kind: "CustomResourceDefinition", Group: "apiextensions.k8s.io"},
75+
},
76+
77+
PhaseStorage: {
78+
{Kind: "PersistentVolume"},
79+
{Kind: "PersistentVolumeClaim"},
80+
{Kind: "StorageClass", Group: "storage.k8s.io"},
81+
},
82+
83+
PhaseDeploy: {
84+
{Kind: "Deployment", Group: "apps"},
85+
{Kind: "DaemonSet", Group: "apps"},
86+
{Kind: "StatefulSet", Group: "apps"},
87+
{Kind: "ReplicaSet"},
88+
{Kind: "Pod"}, // probing complicated, may be either Completed or Available.
89+
{Kind: "Job", Group: "batch"},
90+
{Kind: "CronJob", Group: "batch"},
91+
{Kind: "Service"},
92+
{Kind: "Secret"},
93+
{Kind: "ConfigMap"},
94+
},
95+
96+
PhasePublish: {
97+
{Kind: "Ingress", Group: "networking.k8s.io"},
98+
{Kind: "APIService", Group: "apiregistration.k8s.io"},
99+
{Kind: "Route", Group: "route.openshift.io"},
100+
{Kind: "MutatingWebhookConfiguration", Group: "admissionregistration.k8s.io"},
101+
{Kind: "ValidatingWebhookConfiguration", Group: "admissionregistration.k8s.io"},
102+
},
103+
}
104+
)
105+
106+
func init() {
107+
for phase, gks := range phaseGKMap {
108+
for _, gk := range gks {
109+
gkPhaseMap[gk] = phase
110+
}
111+
}
112+
}
113+
114+
// PhaseSort takes an unsorted list of objects and organizes them into sorted phases.
115+
// Each phase will be applied in order according to DefaultPhaseOrder. Objects
116+
// within a single phase are applied simultaneously.
117+
func PhaseSort(unsortedObjs []ocv1.ClusterExtensionRevisionObject) []ocv1.ClusterExtensionRevisionPhase {
118+
phasesSorted := make([]ocv1.ClusterExtensionRevisionPhase, 0)
119+
phaseMap := make(map[Phase][]ocv1.ClusterExtensionRevisionObject, 0)
120+
121+
for _, obj := range unsortedObjs {
122+
phase := determinePhase(obj.Object.GroupVersionKind().GroupKind())
123+
phaseMap[phase] = append(phaseMap[phase], obj)
124+
}
125+
126+
for _, phaseName := range defaultPhaseOrder {
127+
if objs, ok := phaseMap[phaseName]; ok {
128+
phasesSorted = append(phasesSorted, ocv1.ClusterExtensionRevisionPhase{
129+
Name: string(phaseName),
130+
Objects: objs,
131+
})
132+
}
133+
}
134+
135+
return phasesSorted
136+
}

0 commit comments

Comments
 (0)