Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions api/v1/clusterobjectdeployment_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const (
ClusterObjectDeploymentReasonNewClusterObjectSetProgressed = "NewClusterObjectSetProgressed"
ClusterObjectDeploymentReasonNewClusterObjectSetProgressing = "NewClusterObjectSetProgressing"
ClusterObjectDeploymentReasonProgressDeadlineExceeded = ReasonProgressDeadlineExceeded
ClusterObjectDeploymentReasonInvalidSpecification = "InvalidSpecification"

// Available condition reasons.
ClusterObjectDeploymentReasonAvailable = "Available"
Expand Down Expand Up @@ -65,12 +66,37 @@ type ClusterObjectDeploymentSpec struct {

// ClusterObjectDeploymentTemplate describes the caller-controlled part of a revision.
type ClusterObjectDeploymentTemplate struct {
// metadata contains labels and annotations propagated to each ClusterObjectSet
// created from this template, except the reserved
// olm.operatorframework.io/template-hash label, whose value is always set by the
// controller. Changes to metadata change template identity and therefore create
// a new revision.
// +optional
Metadata ClusterObjectDeploymentTemplateMetadata `json:"metadata,omitzero"`
Comment thread
perdasilva marked this conversation as resolved.

// spec contains the desired revision content. Group, revision, lifecycle state,
// and ownership are assigned by the controller and cannot be set here.
// +required
Spec ClusterObjectDeploymentTemplateSpec `json:"spec,omitzero"`
}

// ClusterObjectDeploymentTemplateMetadata contains labels and annotations
// propagated to each ClusterObjectSet revision created from the template.
type ClusterObjectDeploymentTemplateMetadata struct {
// labels is a set of key/value pairs propagated to each revision's metadata.
// +optional
// +kubebuilder:validation:MaxProperties=32
Labels map[string]string `json:"labels,omitempty"`
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// annotations is a set of key/value pairs propagated to each revision's
// metadata. The controller validates them against the Kubernetes annotation
// rules (key format and total size) before creating a revision; invalid
// annotations are reported on the deployment's Progressing condition.
// +optional
// +kubebuilder:validation:MaxProperties=32
Annotations map[string]string `json:"annotations,omitempty"`
}

// ClusterObjectDeploymentTemplateSpec contains mutable ClusterObjectSet content.
type ClusterObjectDeploymentTemplateSpec struct {
// phases is an ordered list of 1 to 20 uniquely named phases. Each phase contains
Expand Down Expand Up @@ -119,6 +145,9 @@ type ClusterObjectDeploymentStatus struct {
// completion. Completion remains recorded even if the completed revision later
// becomes unhealthy. Progressing is False with reason ProgressDeadlineExceeded when
// the latest revision does not roll out within its spec.progressDeadlineMinutes.
// Progressing is False with reason InvalidSpecification when spec.template.metadata
// contains labels or annotations that are not valid for the resulting
// ClusterObjectSet; no new revision is created until the metadata is corrected.
//
// Available is True with reason Available when at least one active (non-archived)
// ClusterObjectSet revision is Ready, so the deployment remains available across
Expand Down
30 changes: 25 additions & 5 deletions api/v1/clusterobjectdeployment_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,14 +283,10 @@ func TestClusterObjectDeploymentDeferredFieldsArePruned(t *testing.T) {
raw, err := runtime.DefaultUnstructuredConverter.ToUnstructured(cod)
require.NoError(t, err)
require.NoError(t, unstructured.SetNestedField(raw, int64(10), "spec", "progressDeadlineMinutes"))
require.NoError(t, unstructured.SetNestedField(raw, map[string]interface{}{
"labels": map[string]interface{}{"app": "example"},
"annotations": map[string]interface{}{"note": "deferred"},
}, "spec", "template", "metadata"))
obj := &unstructured.Unstructured{Object: raw}
require.NoError(t, c.Create(t.Context(), obj))
require.NoError(t, c.Get(t.Context(), client.ObjectKeyFromObject(obj), obj))
for _, path := range [][]string{{"spec", "progressDeadlineMinutes"}, {"spec", "template", "metadata"}} {
for _, path := range [][]string{{"spec", "progressDeadlineMinutes"}} {
_, found, err := unstructured.NestedFieldNoCopy(obj.Object, path...)
require.NoError(t, err)
require.False(t, found, "deployment must not expose %s", strings.Join(path, "."))
Expand All @@ -311,6 +307,30 @@ func TestClusterObjectDeploymentDeferredFieldsArePruned(t *testing.T) {
}
}

func TestClusterObjectDeploymentTemplateMetadataIsRetained(t *testing.T) {
c := newClient(t)
cod := newTestClusterObjectDeployment("cod-template-metadata")
raw, err := runtime.DefaultUnstructuredConverter.ToUnstructured(cod)
require.NoError(t, err)
require.NoError(t, unstructured.SetNestedField(raw, map[string]interface{}{
"labels": map[string]interface{}{"team": "platform"},
"annotations": map[string]interface{}{"note": "hello"},
}, "spec", "template", "metadata"))
obj := &unstructured.Unstructured{Object: raw}
require.NoError(t, c.Create(t.Context(), obj))
require.NoError(t, c.Get(t.Context(), client.ObjectKeyFromObject(obj), obj))

label, found, err := unstructured.NestedString(obj.Object, "spec", "template", "metadata", "labels", "team")
require.NoError(t, err)
require.True(t, found, "template must expose metadata.labels")
require.Equal(t, "platform", label)

annotation, found, err := unstructured.NestedString(obj.Object, "spec", "template", "metadata", "annotations", "note")
require.NoError(t, err)
require.True(t, found, "template must expose metadata.annotations")
require.Equal(t, "hello", annotation)
}

func getTestClusterObjectDeployment(t *testing.T, c client.Client, name string) *ClusterObjectDeployment {
t.Helper()
cod := &ClusterObjectDeployment{}
Expand Down
50 changes: 43 additions & 7 deletions api/v1/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ func TestValidate(t *testing.T) {
}
type want struct {
valid bool
// errContains is the substring expected in the invalid-create error.
// Required for every case with valid: false.
errContains string
}
type testCase struct {
args args
Expand All @@ -42,6 +45,13 @@ func TestValidate(t *testing.T) {
}
return s
}
manyEntries := func(n int) map[string]string {
m := make(map[string]string, n)
for j := 0; j < n; j++ {
m[fmt.Sprintf("k%d", j)] = "v"
}
return m
}
c := newClient(t)
i := 0

Expand All @@ -52,7 +62,7 @@ func TestValidate(t *testing.T) {
ProgressDeadlineMinutes: 9,
},
},
want: want{valid: false},
want: want{valid: false, errContains: "spec.progressDeadlineMinutes"},
},
"ClusterExtension: valid progress deadline = 10": {
args: args{
Expand Down Expand Up @@ -84,7 +94,7 @@ func TestValidate(t *testing.T) {
ProgressDeadlineMinutes: 721,
},
},
want: want{valid: false},
want: want{valid: false, errContains: "spec.progressDeadlineMinutes"},
},
"ClusterExtension: no progress deadline set": {
args: args{
Expand All @@ -102,7 +112,7 @@ func TestValidate(t *testing.T) {
ProgressDeadlineMinutes: 9,
},
},
want: want{valid: false},
want: want{valid: false, errContains: "spec.progressDeadlineMinutes"},
},
"ClusterObjectSet: valid progress deadline = 10": {
args: args{
Expand Down Expand Up @@ -150,7 +160,7 @@ func TestValidate(t *testing.T) {
ProgressDeadlineMinutes: 721,
},
},
want: want{valid: false},
want: want{valid: false, errContains: "spec.progressDeadlineMinutes"},
},
"ClusterObjectSet: no progress deadline set": {
args: args{
Expand All @@ -171,7 +181,7 @@ func TestValidate(t *testing.T) {
},
},
},
want: want{valid: false},
want: want{valid: false, errContains: "spec.progressDeadlineMinutes"},
},
"ClusterObjectDeployment: valid progress deadline = 10": {
args: args{
Expand Down Expand Up @@ -201,14 +211,39 @@ func TestValidate(t *testing.T) {
},
},
},
want: want{valid: false},
want: want{valid: false, errContains: "spec.progressDeadlineMinutes"},
},
"ClusterObjectDeployment: no progress deadline set": {
args: args{
object: ClusterObjectDeploymentSpec{},
},
want: want{valid: true},
},
"ClusterObjectDeployment: valid template metadata": {
args: args{
object: ClusterObjectDeploymentSpec{
Template: ClusterObjectDeploymentTemplate{
Metadata: ClusterObjectDeploymentTemplateMetadata{
Labels: map[string]string{"team": "platform"},
Annotations: map[string]string{"note": "hello"},
},
},
},
},
want: want{valid: true},
},
"ClusterObjectDeployment: too many template metadata labels": {
args: args{
object: ClusterObjectDeploymentSpec{
Template: ClusterObjectDeploymentTemplate{
Metadata: ClusterObjectDeploymentTemplateMetadata{
Labels: manyEntries(33),
},
},
},
},
want: want{valid: false, errContains: "spec.template.metadata.labels"},
},
} {
t.Run(name, func(t *testing.T) {
var obj client.Object
Expand Down Expand Up @@ -253,7 +288,8 @@ func TestValidate(t *testing.T) {
}
if !tc.want.valid {
require.True(t, errors.IsInvalid(err), "%v", err)
require.ErrorContains(t, err, "spec.progressDeadlineMinutes")
require.NotEmpty(t, tc.want.errContains, "invalid cases must specify want.errContains")
require.ErrorContains(t, err, tc.want.errContains)
}
})
}
Expand Down
30 changes: 30 additions & 0 deletions api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions applyconfigurations/api/v1/clusterobjectdeploymentstatus.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions applyconfigurations/api/v1/clusterobjectdeploymenttemplate.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions applyconfigurations/internal/internal.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading