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
9 changes: 2 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,6 @@ else
$(warning Could not find docker or podman in path! This may result in targets requiring a container runtime failing!)
endif

KUSTOMIZE_STANDARD_OVERLAY := config/overlays/standard
KUSTOMIZE_STANDARD_E2E_OVERLAY := config/overlays/standard-e2e
KUSTOMIZE_EXPERIMENTAL_OVERLAY := config/overlays/experimental
KUSTOMIZE_EXPERIMENTAL_E2E_OVERLAY := config/overlays/experimental-e2e

export STANDARD_RELEASE_MANIFEST := operator-controller.yaml
export STANDARD_RELEASE_INSTALL := install.sh
export EXPERIMENTAL_RELEASE_MANIFEST := operator-controller-experimental.yaml
Expand Down Expand Up @@ -204,8 +199,8 @@ bingo-upgrade: $(BINGO) #EXHELP Upgrade tools
.PHONY: verify-crd-compatibility
CRD_DIFF_ORIGINAL_REF := git://main?path=
CRD_DIFF_UPDATED_REF := file://
CRD_DIFF_OPCON_SOURCE := config/base/operator-controller/crd/standard/olm.operatorframework.io_clusterextensions.yaml
CRD_DIFF_CATD_SOURCE := config/base/catalogd/crd/standard/olm.operatorframework.io_clustercatalogs.yaml
CRD_DIFF_OPCON_SOURCE := helm/olmv1/base/operator-controller/crd/standard/olm.operatorframework.io_clusterextensions.yaml
CRD_DIFF_CATD_SOURCE := helm/olmv1/base/catalogd/crd/standard/olm.operatorframework.io_clustercatalogs.yaml
CRD_DIFF_CONFIG := crd-diff-config.yaml
verify-crd-compatibility: $(CRD_DIFF) manifests
$(CRD_DIFF) --config="${CRD_DIFF_CONFIG}" "${CRD_DIFF_ORIGINAL_REF}${CRD_DIFF_OPCON_SOURCE}" ${CRD_DIFF_UPDATED_REF}${CRD_DIFF_OPCON_SOURCE}
Expand Down
1 change: 1 addition & 0 deletions OWNERS_ALIASES
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ aliases:
- thetechnick
- tmshort
- trgeiger
- pedjak

api-approvers:
- grokspawn
Expand Down
2 changes: 1 addition & 1 deletion api/v1/clustercatalog_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
"sigs.k8s.io/yaml"
)

const crdFilePath = "../../config/base/catalogd/crd/standard/olm.operatorframework.io_clustercatalogs.yaml"
const crdFilePath = "../../helm/olmv1/base/catalogd/crd/standard/olm.operatorframework.io_clustercatalogs.yaml"

func TestImageSourceCELValidationRules(t *testing.T) {
validators := fieldValidatorsFromFile(t, crdFilePath)
Expand Down
12 changes: 8 additions & 4 deletions api/v1/clusterextensionrevision_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,22 @@ type ClusterExtensionRevisionSpec struct {
// +kubebuilder:validation:Enum=Active;Paused;Archived
// +kubebuilder:validation:XValidation:rule="oldSelf == 'Active' || oldSelf == 'Paused' || oldSelf == 'Archived' && oldSelf == self", message="can not un-archive"
LifecycleState ClusterExtensionRevisionLifecycleState `json:"lifecycleState,omitempty"`
// Revision number orders changes over time, must always be previous revision +1.
// Revision is a sequence number representing a specific revision of the ClusterExtension instance.
// Must be positive. Each ClusterExtensionRevision of the same parent ClusterExtension needs to have
// a unique value assigned. It is immutable after creation. The new revision number must always be previous revision +1.
//
// +kubebuilder:validation:Required
// +kubebuilder:validation:Minimum:=1
// +kubebuilder:validation:XValidation:rule="self == oldSelf", message="revision is immutable"
Revision int64 `json:"revision"`
// Phases are groups of objects that will be applied at the same time.
// All objects in the a phase will have to pass their probes in order to progress to the next phase.
// All objects in the phase will have to pass their probes in order to progress to the next phase.
//
// +kubebuilder:validation:Required
// +kubebuilder:validation:XValidation:rule="self == oldSelf || oldSelf.size() == 0", message="phases is immutable"
// +listType=map
// +listMapKey=name
Phases []ClusterExtensionRevisionPhase `json:"phases"`
// +optional
Phases []ClusterExtensionRevisionPhase `json:"phases,omitempty"`
// Previous references previous revisions that objects can be adopted from.
//
// +kubebuilder:validation:XValidation:rule="self == oldSelf", message="previous is immutable"
Expand Down Expand Up @@ -104,6 +107,7 @@ type ClusterExtensionRevisionObject struct {
// already existing on the cluster or even owned by another controller.
//
// +kubebuilder:default="Prevent"
// +optional
CollisionProtection CollisionProtection `json:"collisionProtection,omitempty"`
}

Expand Down
142 changes: 142 additions & 0 deletions api/v1/clusterextensionrevision_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package v1

import (
"context"
"fmt"
"testing"

"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestClusterExtensionRevisionImmutability(t *testing.T) {
c := newClient(t)
ctx := context.Background()
i := 0
for name, tc := range map[string]struct {
spec ClusterExtensionRevisionSpec
updateFunc func(*ClusterExtensionRevision)
allowed bool
}{
"revision is immutable": {
spec: ClusterExtensionRevisionSpec{
Revision: 1,
},
updateFunc: func(cer *ClusterExtensionRevision) {
cer.Spec.Revision = 2
},
},
"phases may be initially empty": {
spec: ClusterExtensionRevisionSpec{
Revision: 1,
Phases: []ClusterExtensionRevisionPhase{},
},
updateFunc: func(cer *ClusterExtensionRevision) {
cer.Spec.Phases = []ClusterExtensionRevisionPhase{
{
Name: "foo",
Objects: []ClusterExtensionRevisionObject{},
},
}
},
allowed: true,
},
"phases may be initially unset": {
spec: ClusterExtensionRevisionSpec{
Revision: 1,
},
updateFunc: func(cer *ClusterExtensionRevision) {
cer.Spec.Phases = []ClusterExtensionRevisionPhase{
{
Name: "foo",
Objects: []ClusterExtensionRevisionObject{},
},
}
},
allowed: true,
},
"phases are immutable if not empty": {
spec: ClusterExtensionRevisionSpec{
Revision: 1,
Phases: []ClusterExtensionRevisionPhase{
{
Name: "foo",
Objects: []ClusterExtensionRevisionObject{},
},
},
},
updateFunc: func(cer *ClusterExtensionRevision) {
cer.Spec.Phases = []ClusterExtensionRevisionPhase{
{
Name: "foo2",
Objects: []ClusterExtensionRevisionObject{},
},
}
},
},
} {
t.Run(name, func(t *testing.T) {
cer := &ClusterExtensionRevision{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("foo%d", i),
},
Spec: tc.spec,
}
i = i + 1
require.NoError(t, c.Create(ctx, cer))
tc.updateFunc(cer)
err := c.Update(ctx, cer)
if tc.allowed && err != nil {
t.Fatal("expected update to succeed, but got:", err)
}
if !tc.allowed && !errors.IsInvalid(err) {
t.Fatal("expected update to fail due to invalid payload, but got:", err)
}
})
}
}

func TestClusterExtensionRevisionValidity(t *testing.T) {
c := newClient(t)
ctx := context.Background()
i := 0
for name, tc := range map[string]struct {
spec ClusterExtensionRevisionSpec
valid bool
}{
"revision cannot be negative": {
spec: ClusterExtensionRevisionSpec{
Revision: -1,
},
valid: false,
},
"revision cannot be zero": {
spec: ClusterExtensionRevisionSpec{},
valid: false,
},
"revision must be positive": {
spec: ClusterExtensionRevisionSpec{
Revision: 1,
},
valid: true,
},
} {
t.Run(name, func(t *testing.T) {
cer := &ClusterExtensionRevision{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("bar%d", i),
},
Spec: tc.spec,
}
i = i + 1
err := c.Create(ctx, cer)
if tc.valid && err != nil {
t.Fatal("expected create to succeed, but got:", err)
}
if !tc.valid && !errors.IsInvalid(err) {
t.Fatal("expected create to fail due to invalid payload, but got:", err)
}
})
}
}
61 changes: 61 additions & 0 deletions api/v1/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
Copyright 2025.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1

import (
"log"
"os"
"testing"

"github.com/stretchr/testify/require"
apimachineryruntime "k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/operator-framework/operator-controller/test"
)

func newScheme(t *testing.T) *apimachineryruntime.Scheme {
sch := apimachineryruntime.NewScheme()
require.NoError(t, AddToScheme(sch))
return sch
}

func newClient(t *testing.T) client.Client {
cl, err := client.New(config, client.Options{Scheme: newScheme(t)})
require.NoError(t, err)
require.NotNil(t, cl)
return cl
}

var config *rest.Config

func TestMain(m *testing.M) {
testEnv := test.NewEnv()

var err error
config, err = testEnv.Start()
utilruntime.Must(err)
if config == nil {
log.Panic("expected cfg to not be nil")
}

code := m.Run()
utilruntime.Must(testEnv.Stop())
os.Exit(code)
}
2 changes: 1 addition & 1 deletion commitchecker.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
expectedMergeBase: 33fdce258350eb563885ee41da087491a15829bd
expectedMergeBase: 0faf118ce37219d518ed380d76101fc3a083a3dd
upstreamBranch: main
upstreamOrg: operator-framework
upstreamRepo: operator-controller
84 changes: 1 addition & 83 deletions config/README.md
Original file line number Diff line number Diff line change
@@ -1,87 +1,5 @@
# OPERATOR-CONTROLLER CONFIGURATION

The main kustomize targets are all located in the `config/overlays` directory. These are the directories that should be passed to kustomize:

e.g.
```
kustomize build config/overlays/standard > standard.yaml
```

# Overlays

All other directories are in support of of these overlays.

## config/overlays/basic-olm

This includes basic support for an insecure (non-TLS) OLMv1 deployment.

## config/overlays/standard

This includes support for a secure (i.e. with TLS) configuration of OLMv1. This configuration requires cert-manager.

This configuration is used to generate `manifests/standard.yaml`.

## config/overlays/standard-e2e

This provides additional configuration support for end-to-end testing, including code coverage. This configuration requires cert-manager.

This configuration is used to generate `manifests/standard-e2e.yaml`.

## config/overlays/prometheus

Overlay containing manifest files which enable prometheus scraping of the catalogd and operator-controller pods. Used during e2e runs to measure performance over the lifetime of the test.

These manifests will not end up in the `manifests/` folder, as they must be applied in two distinct steps to avoid issues with applying prometheus CRDs and CRs simultaneously.

Performance alert settings can be found in: `config/overlays/prometheus/prometheus_rule.yaml`

## config/overlays/experimental

This provides additional configuration used to support experimental features, including CRDs. This configuration requires cert-manager.

This configuration is used to generate `manifests/experimental.yaml`.

## config/overlays/experimental-e2e

This provides experimental configuration and support for end-to-end testing, includng code coverage. This configuration requires cert-manager.

This configuration is used to generate `manifests/experimental-e2e.yaml`.

## config/overlays/tilt-local-dev

This provides configuration for Tilt debugging support.

# Components

Components are the kustomize configuration building blocks.

## config/components/base

This directory provides multiple configurations for organizing the base configuration into standard and experimental configurations.

:bangbang: *The following rules should be followed when configurating a feature:*

* Feature components that are GA'd and should be part of the standard manifest should be listed in `config/components/base/common/kustomization.yaml`. This `commmon` kustomization file is included by *both* the **standard** and **experimental** configurations.
* Feature components that are still experimental and should be part of the standard manifest should be listed only in `config/components/base/experimental/kustomization.yaml`.

## config/components/features

This directory contains contains configuration for features (experimental or otherwise).

:bangbang: *Feature configuration should be placed into a subdirectory here.*

## config/components/cert-manager

This directory provides configuration for using cert-manager with OLMv1.

## config/components/e2e

This directory provides configuration for end-to-end testing of OLMv1.

# Base Configuration

The `config/base` directory contains the base kubebuilder-generated configuration, along with CRDs.

# Samples
## Samples

The `config/samples` directory contains example ClusterCatalog and ClusterExtension resources.
2 changes: 0 additions & 2 deletions config/base/catalogd/crd/OWNERS

This file was deleted.

2 changes: 0 additions & 2 deletions config/base/catalogd/crd/experimental/kustomization.yaml

This file was deleted.

Loading