Skip to content

Commit bed2541

Browse files
stuggiclaude
andcommitted
Add OLM upgrade support to Makefile and document workflow
Add REPLACES variable to inject `replaces` field into the CSV for OLM upgrade paths. Add PREV_BUNDLE_IMG and CATALOG_BUNDLE_IMGS variables to support incremental and from-scratch catalog builds with full upgrade chains. Fix kustomization.yaml patch accumulation by clearing old patches before regenerating. Add documentation covering single-version, multi- version, and complete catalog rebuild workflows. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e678a31 commit bed2541

2 files changed

Lines changed: 270 additions & 2 deletions

File tree

Makefile

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ ifeq ($(USE_IMAGE_DIGESTS), true)
4848
BUNDLE_GEN_FLAGS += --use-image-digests
4949
endif
5050

51+
# REPLACES is the previous version that this version replaces (for OLM upgrades)
52+
# Example: make bundle REPLACES=openstack-operator.v0.6.0 VERSION=0.6.1
53+
REPLACES ?=
54+
5155
# Set the Operator SDK version to use. By default, what is installed on the system is used.
5256
# This is useful for CI or a project to utilize a specific version of the operator-sdk toolkit.
5357
OPERATOR_SDK_VERSION ?= v1.41.1
@@ -398,10 +402,16 @@ endif
398402
.PHONY: bundle
399403
bundle: manifests kustomize operator-sdk ## Generate bundle manifests and metadata, then validate generated files.
400404
$(OPERATOR_SDK) generate kustomize manifests -q
401-
cd config/operator/deployment/ && $(KUSTOMIZE) edit set image controller=$(IMG) && \
405+
cd config/operator/deployment/ && \
406+
sed -i '/^patches:/,$$d' kustomization.yaml && \
407+
$(KUSTOMIZE) edit set image controller=$(IMG) && \
402408
$(KUSTOMIZE) edit add patch --kind Deployment --name openstack-operator-controller-init --namespace system --patch "[{\"op\": \"replace\", \"path\": \"/spec/template/spec/containers/0/env/0\", \"value\": {\"name\": \"OPENSTACK_RELEASE_VERSION\", \"value\": \"$(OPENSTACK_RELEASE_VERSION)\"}}]" && \
403409
$(KUSTOMIZE) edit add patch --kind Deployment --name openstack-operator-controller-init --namespace system --patch "[{\"op\": \"replace\", \"path\": \"/spec/template/spec/containers/0/env/1\", \"value\": {\"name\": \"OPERATOR_IMAGE_URL\", \"value\": \"$(IMG)\"}}]"
404410
$(KUSTOMIZE) build config/operator --load-restrictor='LoadRestrictionsNone' | $(OPERATOR_SDK) generate bundle $(BUNDLE_GEN_FLAGS)
411+
ifneq ($(REPLACES),)
412+
@echo "Adding replaces: $(REPLACES) to CSV"
413+
sed -i "/^ name: openstack-operator.v$(VERSION)/a\ replaces: $(REPLACES)" bundle/manifests/openstack-operator.clusterserviceversion.yaml
414+
endif
405415
$(OPERATOR_SDK) bundle validate ./bundle
406416

407417
.PHONY: bundle-build
@@ -448,12 +458,30 @@ SHELL_EXPORT = $(foreach v,$(MAKE_ENV),$(v)='$($(v))')
448458
# These images MUST exist in a registry and be pull-able.
449459
BUNDLE_IMGS = "$(BUNDLE_IMG)$(shell $(SHELL_EXPORT) /bin/bash hack/pin-bundle-images.sh || echo bundle-fail-tag)"
450460

461+
# When REPLACES is set, include the previous version's bundle in the catalog for upgrade support
462+
# PREV_BUNDLE_IMG can be set to override the default (defaults to upstream :latest)
463+
# Example: make catalog-build REPLACES=openstack-operator.v0.6.0 PREV_BUNDLE_IMG=quay.io/openstack-k8s-operators/openstack-operator-bundle:latest
464+
ifneq ($(REPLACES),)
465+
PREV_BUNDLE_IMG ?= quay.io/openstack-k8s-operators/openstack-operator-bundle:latest
466+
endif
467+
451468
# The image tag given to the resulting catalog image (e.g. make catalog-build CATALOG_IMG=example.com/operator-catalog:v0.2.0).
452469
CATALOG_IMG ?= $(IMAGE_TAG_BASE)-index:v$(VERSION)
453470

454471
# Set CATALOG_BASE_IMG to an existing catalog image tag to add $BUNDLE_IMGS to that image.
472+
# When using a base catalog, only add the NEW bundle (base already has everything else)
473+
# Alternatively, set CATALOG_BUNDLE_IMGS directly to specify all bundles (for building from scratch without a base)
455474
ifneq ($(origin CATALOG_BASE_IMG), undefined)
456475
FROM_INDEX_OPT := --from-index $(CATALOG_BASE_IMG)
476+
CATALOG_BUNDLE_IMGS ?= $(BUNDLE_IMG)
477+
else
478+
# Building from scratch: include previous bundle if REPLACES is set, plus all dependency bundles
479+
# Can be overridden by setting CATALOG_BUNDLE_IMGS on command line
480+
ifneq ($(REPLACES),)
481+
CATALOG_BUNDLE_IMGS ?= $(PREV_BUNDLE_IMG),$(BUNDLE_IMGS)
482+
else
483+
CATALOG_BUNDLE_IMGS ?= $(BUNDLE_IMGS)
484+
endif
457485
endif
458486

459487
# Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'.
@@ -462,7 +490,7 @@ endif
462490
.PHONY: catalog-build
463491
catalog-build: opm ## Build a catalog image.
464492
# FIXME: hardcoded bundle below should use go.mod pinned version for manila bundle
465-
$(OPM) index add --container-tool podman --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT)
493+
$(OPM) index add --container-tool podman --mode semver --tag $(CATALOG_IMG) --bundles $(CATALOG_BUNDLE_IMGS) $(FROM_INDEX_OPT)
466494

467495
# Push the catalog image.
468496
.PHONY: catalog-push
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
# Building Operator Updates for OLM
2+
3+
This guide explains how to build operator versions that support OLM (Operator Lifecycle Manager) upgrades.
4+
5+
## Prerequisites
6+
7+
- An existing operator version is deployed via OLM
8+
- The previous version's bundle exists in a container registry
9+
- You have push access to your container registry
10+
11+
## Simple Update (Single Version Upgrade)
12+
13+
When creating your first update from an upstream version to your custom version:
14+
15+
**Example: Upgrading from upstream v0.6.0 to custom v0.6.1**
16+
17+
```bash
18+
IMAGENAMESPACE=userx \
19+
IMAGE_TAG_BASE=quay.io/userx/openstack-operator \
20+
REPLACES=openstack-operator.v0.6.0 \
21+
VERSION=0.6.1 \
22+
IMG=quay.io/userx/openstack-operator:v0.6.1 \
23+
make manifests generate bindata build docker-build docker-push bundle bundle-build bundle-push catalog-build catalog-push
24+
```
25+
26+
**What this does:**
27+
- Creates v0.6.1 bundle with `replaces: openstack-operator.v0.6.0` in the CSV
28+
- `PREV_BUNDLE_IMG` defaults to `quay.io/openstack-k8s-operators/openstack-operator-bundle:latest` (upstream)
29+
- Builds catalog containing both v0.6.0 (upstream) and v0.6.1 (yours)
30+
- OLM will detect the upgrade path: v0.6.0 → v0.6.1
31+
32+
## Multi-Version Updates (Incremental Upgrades)
33+
34+
When creating subsequent versions on top of your own versions:
35+
36+
**Example: Upgrading from custom v0.6.1 to custom v0.6.2**
37+
38+
```bash
39+
IMAGENAMESPACE=userx \
40+
IMAGE_TAG_BASE=quay.io/userx/openstack-operator \
41+
REPLACES=openstack-operator.v0.6.1 \
42+
VERSION=0.6.2 \
43+
IMG=quay.io/userx/openstack-operator:v0.6.2 \
44+
PREV_BUNDLE_IMG=quay.io/userx/openstack-operator-bundle:v0.6.1 \
45+
CATALOG_BASE_IMG=quay.io/userx/openstack-operator-index:v0.6.1 \
46+
make manifests generate bindata build docker-build docker-push bundle bundle-build bundle-push catalog-build catalog-push
47+
```
48+
49+
**What this does:**
50+
- Creates v0.6.2 bundle with `replaces: openstack-operator.v0.6.1` in the CSV
51+
- `PREV_BUNDLE_IMG` points to your v0.6.1 bundle (not upstream)
52+
- `CATALOG_BASE_IMG` uses your existing v0.6.1 catalog as a base
53+
- Builds catalog containing v0.6.0 → v0.6.1 → v0.6.2
54+
- OLM will detect the upgrade path from v0.6.1 to v0.6.2
55+
56+
## Key Variables Explained
57+
58+
| Variable | Purpose | Example |
59+
|----------|---------|---------|
60+
| `REPLACES` | Previous CSV name that this version replaces | `openstack-operator.v0.6.1` |
61+
| `VERSION` | New version being built | `0.6.2` |
62+
| `IMG` | New operator image | `quay.io/userx/openstack-operator:v0.6.2` |
63+
| `PREV_BUNDLE_IMG` | Bundle image of the version being replaced (used with `REPLACES`) | `quay.io/userx/openstack-operator-bundle:v0.6.1` |
64+
| `CATALOG_BASE_IMG` | Existing catalog to build upon (for incremental builds) | `quay.io/userx/openstack-operator-index:v0.6.1` |
65+
| `CATALOG_BUNDLE_IMGS` | Comma-separated list of all bundles to include (for building complete upgrade path from scratch) | `"bundle1:latest,bundle2:v0.6.1,bundle3:v0.6.2"` |
66+
67+
## Variable Defaults
68+
69+
If not specified:
70+
- `PREV_BUNDLE_IMG` defaults to `quay.io/openstack-k8s-operators/openstack-operator-bundle:latest` (upstream)
71+
- `CATALOG_BASE_IMG` - if not set, builds a fresh catalog from scratch
72+
- `CATALOG_BUNDLE_IMGS` - auto-computed based on other variables (see strategies below)
73+
74+
## Catalog Building Strategies
75+
76+
There are three approaches to building catalogs:
77+
78+
### 1. Incremental Build (Recommended for iterative development)
79+
80+
Use `CATALOG_BASE_IMG` to add the new bundle to an existing catalog:
81+
82+
```bash
83+
IMAGENAMESPACE=userx \
84+
IMAGE_TAG_BASE=quay.io/userx/openstack-operator \
85+
REPLACES=openstack-operator.v0.6.2 \
86+
VERSION=0.6.3 \
87+
IMG=quay.io/userx/openstack-operator:v0.6.3 \
88+
PREV_BUNDLE_IMG=quay.io/userx/openstack-operator-bundle:v0.6.2 \
89+
CATALOG_BASE_IMG=quay.io/userx/openstack-operator-index:v0.6.2 \
90+
make catalog-build catalog-push
91+
```
92+
93+
**Pros:** Fast, builds on existing catalog
94+
**Cons:** Requires previous catalog to be available
95+
96+
### 2. Auto From Scratch (Single Previous Version)
97+
98+
Use `REPLACES` without `CATALOG_BASE_IMG` to include one previous version:
99+
100+
```bash
101+
IMAGENAMESPACE=userx \
102+
IMAGE_TAG_BASE=quay.io/userx/openstack-operator \
103+
REPLACES=openstack-operator.v0.6.2 \
104+
VERSION=0.6.3 \
105+
IMG=quay.io/userx/openstack-operator:v0.6.3 \
106+
PREV_BUNDLE_IMG=quay.io/userx/openstack-operator-bundle:v0.6.2 \
107+
make catalog-build catalog-push
108+
```
109+
110+
**Pros:** Builds from scratch, no base image needed
111+
**Cons:** Only includes one upgrade step (v0.6.2 → v0.6.3), not the full upgrade path
112+
113+
### 3. Full From Scratch (Complete Upgrade Path)
114+
115+
Specify all bundles explicitly with `CATALOG_BUNDLE_IMGS`:
116+
117+
```bash
118+
IMAGENAMESPACE=userx \
119+
IMAGE_TAG_BASE=quay.io/userx/openstack-operator \
120+
VERSION=0.6.3 \
121+
IMG=quay.io/userx/openstack-operator:v0.6.3 \
122+
CATALOG_BUNDLE_IMGS="quay.io/openstack-k8s-operators/openstack-operator-bundle:latest,quay.io/userx/openstack-operator-bundle:v0.6.1,quay.io/userx/openstack-operator-bundle:v0.6.2,quay.io/userx/openstack-operator-bundle:v0.6.3" \
123+
make catalog-build catalog-push
124+
```
125+
126+
**Pros:** Complete control, builds full upgrade path from scratch, reproducible
127+
**Cons:** Must list all bundles manually
128+
129+
**Recommendation:** Use strategy #3 for production releases and strategy #1 for iterative development.
130+
131+
## Deploying the Update
132+
133+
After building and pushing:
134+
135+
1. Update the CatalogSource to use the new catalog image:
136+
```bash
137+
oc patch catalogsource openstack-operator-index \
138+
-n openstack-operators \
139+
--type merge \
140+
-p '{"spec":{"image":"quay.io/userx/openstack-operator-index:v0.6.2"}}'
141+
```
142+
143+
2. Delete the catalog pod to force refresh:
144+
```bash
145+
oc delete pod -n openstack-operators -l olm.catalogSource=openstack-operator-index
146+
```
147+
148+
3. Check for upgrade:
149+
```bash
150+
oc get subscription openstack -n openstack-operators
151+
```
152+
153+
4. Approve the InstallPlan (if using Manual approval):
154+
```bash
155+
oc patch installplan <installplan-name> \
156+
-n openstack-operators \
157+
--type merge \
158+
-p '{"spec":{"approved":true}}'
159+
```
160+
161+
## Troubleshooting
162+
163+
### Upgrade not detected
164+
165+
Check that both versions are in the catalog:
166+
```bash
167+
oc get packagemanifest openstack-operator -n openstack-operators -o json | \
168+
jq -r '.status.channels[] | select(.name=="alpha") | .entries[] | .name'
169+
```
170+
171+
Both the current and new version should appear.
172+
173+
### Bundle image not found
174+
175+
Ensure the previous bundle was pushed:
176+
```bash
177+
skopeo inspect docker://quay.io/userx/openstack-operator-bundle:v0.6.1
178+
```
179+
180+
## Complete Example Workflows
181+
182+
### Approach A: Incremental Builds (Using CATALOG_BASE_IMG)
183+
184+
Building v0.6.1, v0.6.2, and v0.6.3 incrementally:
185+
186+
```bash
187+
# First custom version (from upstream v0.6.0)
188+
IMAGENAMESPACE=userx IMAGE_TAG_BASE=quay.io/userx/openstack-operator \
189+
REPLACES=openstack-operator.v0.6.0 VERSION=0.6.1 \
190+
IMG=quay.io/userx/openstack-operator:v0.6.1 \
191+
make manifests generate bindata build docker-build docker-push bundle bundle-build bundle-push catalog-build catalog-push
192+
193+
# Second version (builds on v0.6.1 catalog)
194+
IMAGENAMESPACE=userx IMAGE_TAG_BASE=quay.io/userx/openstack-operator \
195+
REPLACES=openstack-operator.v0.6.1 VERSION=0.6.2 \
196+
IMG=quay.io/userx/openstack-operator:v0.6.2 \
197+
PREV_BUNDLE_IMG=quay.io/userx/openstack-operator-bundle:v0.6.1 \
198+
CATALOG_BASE_IMG=quay.io/userx/openstack-operator-index:v0.6.1 \
199+
make manifests generate bindata build docker-build docker-push bundle bundle-build bundle-push catalog-build catalog-push
200+
201+
# Third version (builds on v0.6.2 catalog)
202+
IMAGENAMESPACE=userx IMAGE_TAG_BASE=quay.io/userx/openstack-operator \
203+
REPLACES=openstack-operator.v0.6.2 VERSION=0.6.3 \
204+
IMG=quay.io/userx/openstack-operator:v0.6.3 \
205+
PREV_BUNDLE_IMG=quay.io/userx/openstack-operator-bundle:v0.6.2 \
206+
CATALOG_BASE_IMG=quay.io/userx/openstack-operator-index:v0.6.2 \
207+
make manifests generate bindata build docker-build docker-push bundle bundle-build bundle-push catalog-build catalog-push
208+
```
209+
210+
Each catalog contains the full upgrade path from v0.6.0.
211+
212+
### Approach B: Full From Scratch (Using CATALOG_BUNDLE_IMGS)
213+
214+
Building v0.6.3 with complete upgrade path in one step:
215+
216+
```bash
217+
# Build all versions
218+
IMAGENAMESPACE=userx IMAGE_TAG_BASE=quay.io/userx/openstack-operator \
219+
REPLACES=openstack-operator.v0.6.0 VERSION=0.6.1 \
220+
IMG=quay.io/userx/openstack-operator:v0.6.1 \
221+
make manifests generate bindata build docker-build docker-push bundle bundle-build bundle-push
222+
223+
IMAGENAMESPACE=userx IMAGE_TAG_BASE=quay.io/userx/openstack-operator \
224+
REPLACES=openstack-operator.v0.6.1 VERSION=0.6.2 \
225+
IMG=quay.io/userx/openstack-operator:v0.6.2 \
226+
make manifests generate bindata build docker-build docker-push bundle bundle-build bundle-push
227+
228+
IMAGENAMESPACE=userx IMAGE_TAG_BASE=quay.io/userx/openstack-operator \
229+
REPLACES=openstack-operator.v0.6.2 VERSION=0.6.3 \
230+
IMG=quay.io/userx/openstack-operator:v0.6.3 \
231+
make manifests generate bindata build docker-build docker-push bundle bundle-build bundle-push
232+
233+
# Build complete catalog with all bundles
234+
IMAGENAMESPACE=userx IMAGE_TAG_BASE=quay.io/userx/openstack-operator \
235+
VERSION=0.6.3 IMG=quay.io/userx/openstack-operator:v0.6.3 \
236+
CATALOG_BUNDLE_IMGS="quay.io/openstack-k8s-operators/openstack-operator-bundle:latest,quay.io/userx/openstack-operator-bundle:v0.6.1,quay.io/userx/openstack-operator-bundle:v0.6.2,quay.io/userx/openstack-operator-bundle:v0.6.3" \
237+
make catalog-build catalog-push
238+
```
239+
240+
This approach is reproducible and doesn't depend on previous catalog images.

0 commit comments

Comments
 (0)