Skip to content
Open
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,7 @@ junit/
# Buildchain artifacts
/_build/

vagrant_config.rb
vagrant_config.rb

# Binaries downloaded by the upgrade-operator-sdk.py script
/.tmp/
101 changes: 91 additions & 10 deletions BUMPING.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,16 +128,97 @@ A few tips to bump image versions and SHAs:

This guide is applied for both `metalk8s-operator` and `storage-operator`.

- check [documentation](https://sdk.operatorframework.io/docs/upgrading-sdk-version/$version)
for important changes and apply them.
- bump version in Makefile.
- if necessary, bump go version in pre_merge github action.
- if necessary, bump go version in Dockerfile.
- if necessary, bump go dependencies versions.
- in the root of each operator, run `go mod tidy`.
- run `make metalk8s`
- check a diff between the two latest versions of this [test project](https://github.com/operator-framework/operator-sdk/tree/master/testdata/go/v4/memcached-operator)
- the diff in this repo and the test project should be more or less the same
### Prerequisites

- `go`, `curl`, and `patch` in `PATH`.
- `pyyaml` Python package: `pip install pyyaml`
- `GITHUB_TOKEN` (optional): raises the GitHub API rate limit from 60 to 5000
req/hour. Set via `export GITHUB_TOKEN=<token>`.

### Updating the versions

Target versions are pinned in `scripts/upgrade-operator-sdk/<name>/config.yaml`:

```yaml
operator_sdk_version: v1.42.1 # target operator-sdk release
go_toolchain: go1.24.13 # pin Go toolchain (for GOTOOLCHAIN)
k8s_libs: v0.33.10 # pin k8s.io libs version
```

After scaffolding, the script detects the latest available versions (operator-sdk
from GitHub, Go and k8s.io patches from go.dev / module proxy) and compares with
the pinned values:

- **No pin** in YAML: the detected version is used and auto-pinned in the file.
- **Pin matches detected**: all good, no action.
- **Pin is older** than detected: warning printed with the newer version available.
The pinned value is still used. Update the YAML manually when ready.
- **Pin is newer** than detected (unusual): warning, the detected value is used.

This is CI-friendly: zero interactive input during reconciliation.

### Running the upgrade

The script processes one operator at a time:

```bash
python3 scripts/upgrade-operator-sdk/upgrade.py \
--operator-dir operator \
scripts/upgrade-operator-sdk/operator

python3 scripts/upgrade-operator-sdk/upgrade.py \
--operator-dir storage-operator \
scripts/upgrade-operator-sdk/storage-operator
```

Options:

```
--operator-dir Path to the operator project directory (required)
--skip-backup Reuse an existing .bak directory (no new backup)
--clean-tools Remove tool cache after upgrade
--yes, -y Skip the confirmation prompt
```

### YAML config files

Each operator has a config directory at `scripts/upgrade-operator-sdk/<name>/` containing
`config.yaml` and a `patches/` subdirectory. The config fields are:

- **Versions**: `operator_sdk_version`, `go_toolchain` (optional pin), `k8s_libs` (optional pin)
- **Scaffold**: `repo`, `domain`, `apis` (with `group`, `version`, `kind`, `namespaced`). The operator name is derived from the config directory name.
- **Raw copy**: `raw_copy` -- directories or files copied as-is from backup (purely custom code with no scaffold equivalent: `pkg/`, `version/`, `config/metalk8s/`, `salt/`, individual test/helper files)
- **Post-processing**: `extra_commands`

### Patch files

All customizations to scaffold-generated files are stored as GNU unified diff
files in the `patches/` subdirectory. This includes:

- **Dockerfile** and **Makefile** customizations
- **CRD type definitions** (`*_types.go`)
- **Controller implementations** (`*_controller.go`)
- **Scaffold test stubs** (`*_controller_test.go`) -- neutralized when incompatible with the delegation pattern

The script applies them with `patch -p1` after scaffolding. If a patch does not
apply cleanly, look for `.rej` files and resolve manually.

Patch files use `__PLACEHOLDER__` tokens for runtime values:

| Placeholder | Replaced with | Source |
| ----------------- | ---------------------------- | ---------- |
| `__GOTOOLCHAIN__` | Detected/pinned Go toolchain | `Makefile` |

New `.patch` files in the patches directory are automatically picked up.

### What to review after the upgrade

1. `git diff` to review all changes
2. `cd <operator> && make test` to run tests
3. Check `config/crd/bases/` for correct CRD scopes
4. Check `config/rbac/role.yaml` for RBAC completeness
5. Check `deploy/manifests.yaml` for correct Jinja templates
6. Remove backup: `rm -rf <operator>.bak/`

## Calico

Expand Down
38 changes: 38 additions & 0 deletions tools/upgrade-operator-sdk/operator/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
repo: github.com/scality/metalk8s/operator
domain: metalk8s.scality.com

operator_sdk_version: v1.42.1

# Optional: pin versions. If absent, the script detects the latest
# patch from the scaffold's go.mod and auto-pins them here.
go_toolchain: go1.24.13
k8s_libs: v0.33.10

apis:
- version: v1alpha1
kind: ClusterConfig
namespaced: false
- version: v1alpha1
kind: VirtualIPPool
namespaced: true

# Directories/files copied as-is from backup (purely custom, no scaffold equivalent).
raw_copy:
- pkg
- version
- config/metalk8s
- api/v1alpha1/conditions.go
- api/v1alpha1/conditions_test.go
- api/v1alpha1/clusterconfig_types_test.go
- api/v1alpha1/virtualippool_types_test.go
- api/v1alpha1/v1alpha1_suite_test.go

# Files/dirs to delete from the scaffold (not needed or incompatible).
delete:
- .devcontainer
- .github
- internal/controller/clusterconfig_controller_test.go
- internal/controller/virtualippool_controller_test.go

extra_commands:
- ["make", "metalk8s"]
65 changes: 65 additions & 0 deletions tools/upgrade-operator-sdk/operator/patches/Dockerfile.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
--- a/Dockerfile
+++ b/Dockerfile
@@ -15,13 +15,20 @@
COPY cmd/main.go cmd/main.go
COPY api/ api/
COPY internal/ internal/
+COPY pkg/ pkg/
+COPY version/ version/
+
+# Version of the project, e.g. `git describe --always --long --dirty --broken`
+ARG METALK8S_VERSION

# Build
# the GOARCH has not a default value to allow the binary be built according to the host where the command
# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,
# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
-RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go
+RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager \
+ -ldflags "-X 'github.com/scality/metalk8s/operator/version.Version=${METALK8S_VERSION}'" \
+ cmd/main.go

# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
@@ -31,3 +38,40 @@
USER 65532:65532

ENTRYPOINT ["/manager"]
+
+# Timestamp of the build, formatted as RFC3339
+ARG BUILD_DATE
+# Git revision o the tree at build time
+ARG VCS_REF
+# Version of the image
+ARG VERSION
+# Version of the project, e.g. `git describe --always --long --dirty --broken`
+ARG METALK8S_VERSION
+
+# These contain BUILD_DATE so should come 'late' for layer caching
+LABEL maintainer="squad-metalk8s@scality.com" \
+ # http://label-schema.org/rc1/
+ org.label-schema.build-date="$BUILD_DATE" \
+ org.label-schema.name="metalk8s-operator" \
+ org.label-schema.description="Kubernetes Operator for managing MetalK8s cluster config" \
+ org.label-schema.url="https://github.com/scality/metalk8s/" \
+ org.label-schema.vcs-url="https://github.com/scality/metalk8s.git" \
+ org.label-schema.vcs-ref="$VCS_REF" \
+ org.label-schema.vendor="Scality" \
+ org.label-schema.version="$VERSION" \
+ org.label-schema.schema-version="1.0" \
+ # https://github.com/opencontainers/image-spec/blob/master/annotations.md
+ org.opencontainers.image.created="$BUILD_DATE" \
+ org.opencontainers.image.authors="squad-metalk8s@scality.com" \
+ org.opencontainers.image.url="https://github.com/scality/metalk8s/" \
+ org.opencontainers.image.source="https://github.com/scality/metalk8s.git" \
+ org.opencontainers.image.version="$VERSION" \
+ org.opencontainers.image.revision="$VCS_REF" \
+ org.opencontainers.image.vendor="Scality" \
+ org.opencontainers.image.title="metalk8s-operator" \
+ org.opencontainers.image.description="Kubernetes Operator for managing MetalK8s cluster config" \
+ # https://docs.openshift.org/latest/creating_images/metadata.html
+ io.openshift.tags="metalk8s,operator" \
+ io.k8s.description="Kubernetes Operator for managing MetalK8s cluster config" \
+ # Various
+ com.scality.metalk8s.version="$METALK8S_VERSION"
15 changes: 15 additions & 0 deletions tools/upgrade-operator-sdk/operator/patches/Makefile.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--- a/Makefile
+++ b/Makefile
@@ -3,3 +3,12 @@
.PHONY: catalog-push
catalog-push: ## Push a catalog image.
$(MAKE) docker-push IMG=$(CATALOG_IMG)
+
+# Force Go toolchain version
+export GOTOOLCHAIN = __GOTOOLCHAIN__
+
+.PHONY: metalk8s
+metalk8s: manifests kustomize ## Generate MetalK8s resulting manifests
+ mkdir -p deploy
+ $(KUSTOMIZE) build config/metalk8s | \
+ sed 's/BUILD_IMAGE_CLUSTER_OPERATOR:latest/{{ build_image_name("metalk8s-operator") }}/' > deploy/manifests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
--- a/internal/controller/clusterconfig_controller.go
+++ b/internal/controller/clusterconfig_controller.go
@@ -17,14 +17,10 @@
package controller

import (
- "context"
-
+ "github.com/scality/metalk8s/operator/pkg/controller/clusterconfig"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
- logf "sigs.k8s.io/controller-runtime/pkg/log"
-
- metalk8sscalitycomv1alpha1 "github.com/scality/metalk8s/operator/api/v1alpha1"
)

// ClusterConfigReconciler reconciles a ClusterConfig object
@@ -37,27 +33,11 @@
// +kubebuilder:rbac:groups=metalk8s.scality.com,resources=clusterconfigs/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=metalk8s.scality.com,resources=clusterconfigs/finalizers,verbs=update

-// Reconcile is part of the main kubernetes reconciliation loop which aims to
-// move the current state of the cluster closer to the desired state.
-// TODO(user): Modify the Reconcile function to compare the state specified by
-// the ClusterConfig object against the actual cluster state, and then
-// perform operations to make the cluster state reflect the state specified by
-// the user.
-//
-// For more details, check Reconcile and its Result here:
-// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile
-func (r *ClusterConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
- _ = logf.FromContext(ctx)
-
- // TODO(user): your logic here
-
- return ctrl.Result{}, nil
-}
+// +kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch
+// +kubebuilder:rbac:groups=metalk8s.scality.com,resources=virtualippools,verbs=get;list;watch;create;update;patch;delete

// SetupWithManager sets up the controller with the Manager.
func (r *ClusterConfigReconciler) SetupWithManager(mgr ctrl.Manager) error {
- return ctrl.NewControllerManagedBy(mgr).
- For(&metalk8sscalitycomv1alpha1.ClusterConfig{}).
- Named("clusterconfig").
- Complete(r)
+ return clusterconfig.Add(mgr)
}
Loading
Loading