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
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Temporary Build Files
build/_output
build/_test
catalog/
# Created by https://www.gitignore.io/api/go,vim,emacs,visualstudiocode
### Emacs ###
# -*- mode: gitignore; -*-
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ jobs:
make manifests
make api-gen
make docs
make olm
# check for uncommited changes to crds, docs or API
git diff --exit-code
make test
Expand Down
60 changes: 37 additions & 23 deletions .github/workflows/operatorhub.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
repository: ${{ matrix.repo.upstream }}
ref: main
token: ${{ secrets.VM_BOT_GH_TOKEN }}
path: __k8s-operatorhub-repo
path: __operatorhub-repo

- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v6
Expand All @@ -48,58 +48,72 @@ jobs:
passphrase: ${{ secrets.VM_BOT_PASSPHRASE }}
git_user_signingkey: true
git_commit_gpgsign: true
workdir: __k8s-operatorhub-repo
workdir: __operatorhub-repo

- uses: dawidd6/action-download-artifact@v11
with:
name: olm
workflow: main.yaml
workflow: release.yaml
github_token: ${{ secrets.VM_BOT_GH_TOKEN }}
run_id: ${{ github.event.workflow_run.id }}
path: bundle

- name: Install opm
run: |
OPM_VERSION=v1.65.0
curl -fsSLO https://github.com/operator-framework/operator-registry/releases/download/${OPM_VERSION}/linux-amd64-opm
curl -fsSLO https://github.com/operator-framework/operator-registry/releases/download/${OPM_VERSION}/checksums.txt
grep ' linux-amd64-opm$' checksums.txt | sha256sum -c -
install -m 0755 linux-amd64-opm /usr/local/bin/opm

- name: Add operatorhub bundle
id: update
run: |
if [ ! -d bundle ]; then
echo "No bundle directory found"
exit 1;
fi
OPERATOR_DIR=__k8s-operatorhub-repo/operators/victoriametrics-operator
CATALOGS_DIR=__k8s-operatorhub-repo/catalogs
CATALOGS_DIR=__operatorhub-repo/catalogs
NEW_VERSION=$(ls bundle | head -1)
CATALOG_FILE=${CATALOGS_DIR}/latest/victoriametrics-operator/catalog.yaml
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Initialize PREV_CATALOG to a valid default before conditional assignment; it is currently used unconditionally and can break the workflow when no prior catalog exists.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/operatorhub.yaml, line 75:

<comment>Initialize `PREV_CATALOG` to a valid default before conditional assignment; it is currently used unconditionally and can break the workflow when no prior catalog exists.</comment>

<file context>
@@ -48,58 +48,69 @@ jobs:
-          CATALOGS_DIR=__k8s-operatorhub-repo/catalogs
+          CATALOGS_DIR=__operatorhub-repo/catalogs
+          NEW_VERSION=$(ls bundle | head -1)
+          CATALOG_FILE=${CATALOGS_DIR}/latest/victoriametrics-operator/catalog.yaml
+          mkdir -p ${CATALOGS_DIR}/latest/victoriametrics-operator
 
</file context>
Suggested change
CATALOG_FILE=${CATALOGS_DIR}/latest/victoriametrics-operator/catalog.yaml
CATALOG_FILE=${CATALOGS_DIR}/latest/victoriametrics-operator/catalog.yaml
PREV_CATALOG=/dev/null
Fix with Cubic

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not an issue - prior catalog exists

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for clarifying—the prior catalog exists, so this isn’t an issue here.

mkdir -p ${CATALOGS_DIR}/latest/victoriametrics-operator

mkdir -p ${CATALOGS_DIR}
mkdir -p ${OPERATOR_DIR}
# Save existing catalog as PREV_CATALOG before overwriting
if [ -f "${CATALOG_FILE}" ]; then
cp "${CATALOG_FILE}" /tmp/vm-prev-catalog.yaml
PREV_CATALOG=/tmp/vm-prev-catalog.yaml
fi

export OLD_VERSION=$(find ${OPERATOR_DIR}/* ! -path "*/catalog-templates" -maxdepth 0 -type d -exec basename {} \; | sort -V -r | head -1)
export OLD_ENTRY="victoriametrics-operator.v${OLD_VERSION}"
PREVIOUS_VERSION=$(yq 'select(.schema == "olm.channel") | .entries[0].name' \
${PREV_CATALOG} 2>/dev/null || true)

export NEW_VERSION=$(ls bundle | head -1)
opm render bundle/${NEW_VERSION} --output=yaml > /tmp/vm-bundle-rendered.yaml
yq 'select(.schema == "olm.package")' "${PREV_CATALOG}" > /tmp/vm-catalog-header.yaml
echo "---" >> /tmp/vm-catalog-header.yaml
yq '.entries[] | select(.schema == "olm.channel")' \
bundle/${NEW_VERSION}/catalog-templates/latest.yaml >> /tmp/vm-catalog-header.yaml

if [ ! -z $OLD_VERSION ]; then
export MANIFEST_PATH=bundle/${NEW_VERSION}/manifests/victoriametrics-operator.clusterserviceversion.yaml
yq -i '.spec.replaces = "victoriametrics-operator.v" + strenv(OLD_VERSION)' $MANIFEST_PATH
fi
# Include previous bundles from PREV_CATALOG (excludes current version to avoid duplicates)
yq "select(.schema == \"olm.bundle\" and .name != \"victoriametrics-operator.v${NEW_VERSION}\")" \
"${PREV_CATALOG}" > /tmp/vm-prev-bundles.yaml
yq eval-all '.' /tmp/vm-catalog-header.yaml /tmp/vm-bundle-rendered.yaml /tmp/vm-prev-bundles.yaml > ${CATALOG_FILE}

mv bundle/* ${OPERATOR_DIR}/
if [ -f ${OPERATOR_DIR}/Makefile ]; then
if [ ! -z $OLD_VERSION ]; then
yq -i -I2 '.catalog_templates.[].replaces = strenv(OLD_ENTRY)' ${OPERATOR_DIR}/${NEW_VERSION}/release-config.yaml
fi
else
rm -f ${OPERATOR_DIR}/${NEW_VERSION}/release-config.yaml
if [ -n "${PREVIOUS_VERSION}" ]; then
yq -i '(select(.schema == "olm.channel") | .entries[0]).replaces = strenv(PREVIOUS_VERSION)' ${CATALOG_FILE}
fi
opm validate ${CATALOGS_DIR}/latest/victoriametrics-operator

echo "VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT

- name: Create Pull Request
if: ${{ steps.update.outputs.VERSION != '' }}
uses: peter-evans/create-pull-request@v7
with:
add-paths: operators/victoriametrics-operator,catalogs
add-paths: catalogs
commit-message: 'victoriametrics-operator: ${{ steps.update.outputs.VERSION }}'
signoff: true
committer: "Github Actions <${{ steps.import-gpg.outputs.email }}>"
path: __k8s-operatorhub-repo
path: __operatorhub-repo
push-to-fork: ${{ matrix.repo.fork }}
branch: vm-operator-release-${{ steps.update.outputs.VERSION }}
token: ${{ secrets.VM_BOT_GH_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
make lint test build build-installer
echo ${{secrets.REPO_KEY}} | docker login --username ${{secrets.REPO_USER}} --password-stdin
echo ${{secrets.QUAY_ACCESSKEY}} | docker login quay.io --username '${{secrets.QUAY_USER}}' --password-stdin
make publish
TAG=${TAG} make publish
TAG=${TAG} REGISTRY=quay.io make olm
gh release upload ${{github.event.release.tag_name}} ./dist/install-no-webhook.yaml#install-no-webhook.yaml --clobber || echo "fix me NOT enough security permissions"
gh release upload ${{github.event.release.tag_name}} ./dist/install-with-webhook.yaml#install-with-webhook.yaml --clobber || echo "fix me NOT enough security permissions"
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ report.xml
/bin/
/build*
/bundle*
/catalog*
release
operator.zip
coverage.txt
Expand Down
47 changes: 26 additions & 21 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ DATEINFO_TAG ?= $(shell date -u +'%Y%m%d-%H%M%S')
NAMESPACE ?= vm
OVERLAY ?= config/manager
E2E_TESTS_CONCURRENCY ?= $(shell getconf _NPROCESSORS_ONLN)
E2E_TARGET ?= ./test/e2e/...
FIPS_VERSION=v1.0.0
BASEIMAGE ?=scratch

Expand Down Expand Up @@ -160,26 +161,18 @@ test: manifests generate fmt vet envtest ## Run tests.

# Utilize Kind or modify the e2e tests to load the image locally, enabling compatibility with other vendors.
.PHONY: test-e2e # Run the e2e tests against a Kind k8s instance that is spun up.
BASE_REF ?= origin/master
SKIP_UPGRADE_TESTS ?= $(shell if git diff --quiet $(BASE_REF)...HEAD -- test/e2e/upgrade 2>/dev/null; then echo "--skip-package=upgrade"; fi)

test-e2e: load-kind ginkgo crust-gather mirrord
env CGO_ENABLED=1 OPERATOR_IMAGE=$(OPERATOR_IMAGE) REPORTS_DIR=$(shell pwd) CRUST_GATHER_BIN=$(CRUST_GATHER_BIN) $(MIRRORD_BIN) exec -f ./mirrord.json -- $(GINKGO_BIN) \
-ldflags="-linkmode=external" \
$(SKIP_UPGRADE_TESTS) \
--output-interceptor-mode=none \
-procs=$(E2E_TESTS_CONCURRENCY) \
-randomize-all \
-timeout=60m \
-junit-report=report.xml ./test/e2e/...
-junit-report=report.xml $(E2E_TARGET)

.PHONY: test-e2e-upgrade # Run only the e2e upgrade tests against a Kind k8s instance that is spun up.
test-e2e-upgrade: load-kind ginkgo crust-gather mirrord
env CGO_ENABLED=1 OPERATOR_IMAGE=$(OPERATOR_IMAGE) REPORTS_DIR=$(shell pwd) CRUST_GATHER_BIN=$(CRUST_GATHER_BIN) $(MIRRORD_BIN) exec -f ./mirrord.json -- $(GINKGO_BIN) \
-ldflags="-linkmode=external" \
-procs=$(E2E_TESTS_CONCURRENCY) \
-randomize-all \
-timeout=60m \
-junit-report=report.xml ./test/e2e/upgrade/...
test-e2e-upgrade: E2E_TARGET=./test/e2e/upgrade/...
test-e2e-upgrade: test-e2e

.PHONY: lint
lint: golangci-lint ## Run golangci-lint linter
Expand Down Expand Up @@ -281,25 +274,37 @@ build-installer: manifests generate kustomize ## Generate a consolidated YAML wi

olm: operator-sdk opm yq docs
$(eval DIGEST = $(shell $(CONTAINER_TOOL) buildx imagetools inspect $(REGISTRY)/$(ORG)/$(REPO):$(TAG)-ubi --format "{{print .Manifest.Digest}}"))
rm -rf bundle*
rm -rf bundle* catalog
$(OPERATOR_SDK) generate kustomize manifests -q
cd config/manifests && \
$(KUSTOMIZE) edit set image manager=$(REGISTRY)/$(ORG)/$(REPO)@$(DIGEST)
$(KUSTOMIZE) build config/manifests | $(OPERATOR_SDK) generate bundle \
-q --overwrite --version $(VERSION) \
--channels=beta --default-channel=beta --output-dir=bundle/$(VERSION)
$(OPERATOR_SDK) bundle validate ./bundle/$(VERSION)
cp config/manifests/release-config.yaml bundle/$(VERSION)/
$(YQ) -i '.metadata.annotations.containerImage = "$(REGISTRY)/$(ORG)/$(REPO)@$(DIGEST)"' \
bundle/$(VERSION)/manifests/victoriametrics-operator.clusterserviceversion.yaml
$(YQ) -i '.spec.install.spec.deployments[0].spec.template.containers[0].image = "$(REGISTRY)/$(ORG)/$(REPO)@$(DIGEST)"' \
bundle/$(VERSION)/manifests/victoriametrics-operator.clusterserviceversion.yaml
$(YQ) -i '.spec.install.spec.deployments[0].spec.template.spec.containers[0].image = "$(REGISTRY)/$(ORG)/$(REPO)@$(DIGEST)"' \
bundle/$(VERSION)/manifests/victoriametrics-operator.clusterserviceversion.yaml
$(YQ) -i '.spec.relatedImages = [{"name": "victoriametrics-operator", "image": "$(REGISTRY)/$(ORG)/$(REPO)@$(DIGEST)"}]' \
$(YQ) -i '.metadata.annotations.containerImage = "$(REGISTRY)/$(ORG)/$(REPO)@$(DIGEST)" | .spec.install.spec.deployments[0].spec.template.containers[0].image = "$(REGISTRY)/$(ORG)/$(REPO)@$(DIGEST)" | .spec.install.spec.deployments[0].spec.template.spec.containers[0].image = "$(REGISTRY)/$(ORG)/$(REPO)@$(DIGEST)"' \
bundle/$(VERSION)/manifests/victoriametrics-operator.clusterserviceversion.yaml
$(YQ) -i '.annotations."com.redhat.openshift.versions" = "v4.12-v4.21"' \
bundle/$(VERSION)/metadata/annotations.yaml
mkdir -p bundle/$(VERSION)/catalog-templates catalog/latest
$(YQ) '.entries[] | select(.schema == "olm.channel") | .entries[] | select(.name != "victoriametrics-operator.v$(VERSION)") | .name' config/manifests/catalog-templates/latest.yaml > /tmp/vm-prev-names.txt
$(YQ) '.entries[] | select(.schema == "olm.channel") | .entries[] | select(.name != "victoriametrics-operator.v$(VERSION)") | .replaces | select(.)' config/manifests/catalog-templates/latest.yaml > /tmp/vm-prev-replaces.txt
PREV_HEAD=$$(grep -Fxvf /tmp/vm-prev-replaces.txt /tmp/vm-prev-names.txt | head -1); \
test -n "$$PREV_HEAD" || { echo "Error: could not determine previous channel head from catalog template"; exit 1; }; \
PREV_HEAD="$$PREV_HEAD" $(YQ) -i '(.entries[] | select(.schema == "olm.channel")).entries = [{"name": "victoriametrics-operator.v$(VERSION)", "replaces": strenv(PREV_HEAD)}] + (.entries[] | select(.schema == "olm.channel") | .entries | map(select(.name != "victoriametrics-operator.v$(VERSION)")))' \
config/manifests/catalog-templates/latest.yaml; \
PREV_HEAD="$$PREV_HEAD" $(YQ) -i '.spec.relatedImages = [{"name": "victoriametrics-operator", "image": "$(REGISTRY)/$(ORG)/$(REPO)@$(DIGEST)"}] | .spec.replaces = strenv(PREV_HEAD)' \
bundle/$(VERSION)/manifests/victoriametrics-operator.clusterserviceversion.yaml
cp config/manifests/catalog-templates/latest.yaml bundle/$(VERSION)/catalog-templates/latest.yaml
{ $(YQ) '.entries[] | select(.schema == "olm.package")' \
bundle/$(VERSION)/catalog-templates/latest.yaml; \
echo "---"; \
$(YQ) '(.entries[] | select(.schema == "olm.channel")) | .entries = [.entries[0]]' \
bundle/$(VERSION)/catalog-templates/latest.yaml; \
$(OPM) render bundle/$(VERSION) --output=yaml | \
$(YQ) '(select(.schema == "olm.bundle") | .image) = "$(REGISTRY)/$(ORG)/$(REPO)@$(DIGEST)" | (select(.schema == "olm.bundle") | .relatedImages) = [{"name": "victoriametrics-operator", "image": "$(REGISTRY)/$(ORG)/$(REPO)@$(DIGEST)"}]'; \
} > catalog/latest/catalog.yaml
$(OPM) validate catalog/latest

##@ Deployment

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

import (
"testing"

"github.com/stretchr/testify/assert"
"k8s.io/utils/ptr"

vmv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
)

func TestVTCluster_AvailableStorageNodeIDs(t *testing.T) {
f := func(cr *VTCluster, requestsType string, want []int32) {
t.Helper()
assert.Equal(t, want, cr.AvailableStorageNodeIDs(requestsType))
}

cr := &VTCluster{
Spec: VTClusterSpec{
Storage: &VTStorage{
CommonAppsParams: vmv1beta1.CommonAppsParams{
ReplicaCount: ptr.To(int32(5)),
},
MaintenanceSelectNodeIDs: []int32{1, 3},
MaintenanceInsertNodeIDs: []int32{0, 4},
},
},
}

// select excludes maintenance nodes
f(cr, "select", []int32{0, 2, 4})

// insert excludes maintenance nodes
f(cr, "insert", []int32{1, 2, 3})

// no maintenance nodes
f(&VTCluster{
Spec: VTClusterSpec{
Storage: &VTStorage{
CommonAppsParams: vmv1beta1.CommonAppsParams{ReplicaCount: ptr.To(int32(3))},
},
},
}, "select", []int32{0, 1, 2})
}

func TestVLCluster_AvailableStorageNodeIDs(t *testing.T) {
f := func(cr *VLCluster, requestsType string, want []int32) {
t.Helper()
assert.Equal(t, want, cr.AvailableStorageNodeIDs(requestsType))
}

cr := &VLCluster{
Spec: VLClusterSpec{
VLStorage: &VLStorage{
CommonAppsParams: vmv1beta1.CommonAppsParams{
ReplicaCount: ptr.To(int32(5)),
},
MaintenanceSelectNodeIDs: []int32{1, 3},
MaintenanceInsertNodeIDs: []int32{0, 4},
},
},
}

// select excludes maintenance nodes
f(cr, "select", []int32{0, 2, 4})

// insert excludes maintenance nodes
f(cr, "insert", []int32{1, 2, 3})

// no maintenance nodes
f(&VLCluster{
Spec: VLClusterSpec{
VLStorage: &VLStorage{
CommonAppsParams: vmv1beta1.CommonAppsParams{ReplicaCount: ptr.To(int32(3))},
},
},
}, "select", []int32{0, 1, 2})
}
12 changes: 4 additions & 8 deletions api/operator/v1/vlcluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -781,21 +781,17 @@ func (cr *VLCluster) AvailableStorageNodeIDs(requestsType string) []int32 {
if cr.Spec.VLStorage == nil || cr.Spec.VLStorage.ReplicaCount == nil {
return result
}
maintenanceNodes := make(map[int32]struct{})
maintenanceNodes := sets.New[int32]()
switch requestsType {
case "select":
for _, i := range cr.Spec.VLStorage.MaintenanceSelectNodeIDs {
maintenanceNodes[i] = struct{}{}
}
maintenanceNodes.Insert(cr.Spec.VLStorage.MaintenanceSelectNodeIDs...)
case "insert":
for _, i := range cr.Spec.VLStorage.MaintenanceInsertNodeIDs {
maintenanceNodes[i] = struct{}{}
}
maintenanceNodes.Insert(cr.Spec.VLStorage.MaintenanceInsertNodeIDs...)
default:
panic("BUG unsupported requestsType: " + requestsType)
}
for i := int32(0); i < *cr.Spec.VLStorage.ReplicaCount; i++ {
if _, ok := maintenanceNodes[i]; ok {
if maintenanceNodes.Has(i) {
continue
}
result = append(result, i)
Expand Down
12 changes: 4 additions & 8 deletions api/operator/v1/vtcluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -692,21 +692,17 @@ func (cr *VTCluster) AvailableStorageNodeIDs(requestsType string) []int32 {
if cr.Spec.Storage == nil || cr.Spec.Storage.ReplicaCount == nil {
return result
}
maintenanceNodes := make(map[int32]struct{})
maintenanceNodes := sets.New[int32]()
switch requestsType {
case "select":
for _, i := range cr.Spec.Storage.MaintenanceSelectNodeIDs {
maintenanceNodes[i] = struct{}{}
}
maintenanceNodes.Insert(cr.Spec.Storage.MaintenanceSelectNodeIDs...)
case "insert":
for _, i := range cr.Spec.Storage.MaintenanceInsertNodeIDs {
maintenanceNodes[i] = struct{}{}
}
maintenanceNodes.Insert(cr.Spec.Storage.MaintenanceInsertNodeIDs...)
default:
panic("BUG unsupported requestsType: " + requestsType)
}
for i := int32(0); i < *cr.Spec.Storage.ReplicaCount; i++ {
if _, ok := maintenanceNodes[i]; ok {
if maintenanceNodes.Has(i) {
continue
}
result = append(result, i)
Expand Down
Loading
Loading