Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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/
86 changes: 76 additions & 10 deletions BUMPING.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,16 +128,82 @@ 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`

### Updating the versions

Before running the script, update the target versions in the YAML config files at
`scripts/upgrade-operator-sdk/<name>/config.yaml`:

```yaml
operator_sdk_version: v1.42.1 # target operator-sdk release
go_toolchain: go1.25.8 # Go toolchain (for GOTOOLCHAIN + FROM golang:X.Y)
k8s_libs: v0.33.9 # k8s.io/{api,apimachinery,client-go} version
```

The script makes **no version-detection API calls**; all versions are read from the
YAML config.

### Running the upgrade

The script processes one operator at a time. Run it once per operator:

```bash
python3 scripts/upgrade-operator-sdk/upgrade.py operator
python3 scripts/upgrade-operator-sdk/upgrade.py storage-operator
```

The argument is the name of the config directory next to the script
(i.e. `scripts/upgrade-operator-sdk/<name>/`). A full path can also be
given for configs stored elsewhere.

Options:

```
--skip-backup Reuse an existing .bak directory (no new backup)
--clean-tools Delete .tmp/bin/ after the 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`, `k8s_libs`
- **Scaffold**: `repo`, `domain`, `apis` (with `group`, `version`, `kind`, `namespaced`). The operator name is derived from the config directory name.
- **Paths**: `operator_dir`, `patches_dir`, `backup_paths`
- **Post-processing**: `image_placeholder`, `extra_commands`

### Patch files

MetalK8s-specific customizations to scaffold-generated files (`Dockerfile`, `Makefile`)
are stored as GNU unified diff files in the `patches/` subdirectory next to `config.yaml`. 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 values from the YAML config:

| Placeholder | Replaced with | Source |
| ----------------- | -------------------------------------------- | ---------- |
| `__GOTOOLCHAIN__` | `go_toolchain` from config (e.g. `go1.25.8`) | `Makefile` |
| `__IMAGE__` | `image_placeholder` from config | `Makefile` |

The `FROM golang:X.Y` in `Dockerfile` is derived from `go_toolchain` in the config.

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
28 changes: 28 additions & 0 deletions scripts/upgrade-operator-sdk/operator/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
repo: github.com/scality/metalk8s/operator
domain: metalk8s.scality.com
operator_dir: operator

operator_sdk_version: v1.42.1
go_toolchain: go1.25.8
k8s_libs: v0.33.9

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

backup_paths:
- pkg/
- version/
- config/metalk8s/
- api/
- hack/
- internal/controller/

image_placeholder: '{{ build_image_name("metalk8s-operator") }}'

extra_commands:
- ["make", "metalk8s"]
65 changes: 65 additions & 0 deletions scripts/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"
19 changes: 19 additions & 0 deletions scripts/upgrade-operator-sdk/operator/patches/Makefile.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--- a/Makefile
+++ b/Makefile
@@ -1,2 +1,6 @@
#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31)
+
+# Force Go toolchain version to prevent automatic selection issues
+# See: https://go.dev/doc/toolchain
+export GOTOOLCHAIN = __GOTOOLCHAIN__
ENVTEST_K8S_VERSION ?= $(shell go list -m -f "{{ .Version }}" k8s.io/api | awk -F'[v.]' '{printf "1.%d", $$3}')
@@ -4,3 +8,9 @@
.PHONY: catalog-push
catalog-push: ## Push a catalog image.
$(MAKE) docker-push IMG=$(CATALOG_IMG)
+
+.PHONY: metalk8s
+metalk8s: manifests kustomize ## Generate MetalK8s resulting manifests
+ mkdir -p deploy
+ $(KUSTOMIZE) build config/metalk8s | \
+ sed 's/BUILD_IMAGE_CLUSTER_OPERATOR:latest/__IMAGE__/' > deploy/manifests.yaml
39 changes: 39 additions & 0 deletions scripts/upgrade-operator-sdk/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Linting and formatting configuration for scripts/upgrade-operator-sdk/upgrade.py
# Run from the scripts/ directory:
# python3 -m black upgrade-operator-sdk/upgrade.py
# python3 -m ruff check upgrade-operator-sdk/upgrade.py
# python3 -m mypy upgrade-operator-sdk/upgrade.py

[tool.black]
line-length = 88
target-version = ["py310"]

[tool.ruff]
line-length = 88
target-version = "py310"

[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes (undefined names, unused imports, …)
"I", # isort (import ordering)
"N", # pep8-naming conventions
"UP", # pyupgrade (modernise Python syntax)
"B", # flake8-bugbear (likely bugs and design issues)
"C4", # flake8-comprehensions (better list/dict/set comprehensions)
"SIM", # flake8-simplify (simplifiable code patterns)
"RET", # flake8-return (return statement issues)
"PTH", # flake8-use-pathlib (prefer pathlib over os.path)
"TRY", # tryceratops (exception handling anti-patterns)
]
ignore = [
"RET504", # allow x = ...; return x (readability)
"TRY003", # allow long messages in raise/die() calls
"TRY300", # allow return inside try block
]

[tool.mypy]
strict = true
ignore_missing_imports = true
python_version = "3.10"
25 changes: 25 additions & 0 deletions scripts/upgrade-operator-sdk/storage-operator/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
repo: github.com/scality/metalk8s/storage-operator
domain: metalk8s.scality.com
operator_dir: storage-operator

operator_sdk_version: v1.42.1
go_toolchain: go1.25.8
k8s_libs: v0.33.9

apis:
- group: storage
version: v1alpha1
kind: Volume
namespaced: false

backup_paths:
- api/
- hack/
- internal/controller/
- config/metalk8s/
- salt/

image_placeholder: '{{ build_image_name("storage-operator") }}'

extra_commands:
- ["make", "metalk8s"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
--- a/Dockerfile
+++ b/Dockerfile
@@ -15,6 +15,7 @@
COPY cmd/main.go cmd/main.go
COPY api/ api/
COPY internal/ internal/
+COPY salt/ salt/

# Build
# the GOARCH has not a default value to allow the binary be built according to the host where the command
@@ -31,3 +32,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="storage-operator" \
+ org.label-schema.description="Kubernetes Operator for managing PersistentVolumes in MetalK8s" \
+ 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="storage-operator" \
+ org.opencontainers.image.description="Kubernetes Operator for managing PersistentVolumes in MetalK8s" \
+ # https://docs.openshift.org/latest/creating_images/metadata.html
+ io.openshift.tags="metalk8s,storage,operator" \
+ io.k8s.description="Kubernetes Operator for managing PersistentVolumes in MetalK8s" \
+ # Various
+ com.scality.metalk8s.version="$METALK8S_VERSION"
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--- a/Makefile
+++ b/Makefile
@@ -1,2 +1,6 @@
#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31)
+
+# Force Go toolchain version to prevent automatic selection issues
+# See: https://go.dev/doc/toolchain
+export GOTOOLCHAIN = __GOTOOLCHAIN__
ENVTEST_K8S_VERSION ?= $(shell go list -m -f "{{ .Version }}" k8s.io/api | awk -F'[v.]' '{printf "1.%d", $$3}')
@@ -4,3 +8,9 @@
.PHONY: catalog-push
catalog-push: ## Push a catalog image.
$(MAKE) docker-push IMG=$(CATALOG_IMG)
+
+.PHONY: metalk8s
+metalk8s: manifests kustomize ## Generate MetalK8s resulting manifests
+ mkdir -p deploy
+ $(KUSTOMIZE) build config/metalk8s | \
+ sed 's/BUILD_IMAGE_CLUSTER_OPERATOR:latest/__IMAGE__/' > deploy/manifests.yaml
Loading
Loading