diff --git a/.github/ISSUE_TEMPLATE/new_release.md b/.github/ISSUE_TEMPLATE/new_release.md
index 5378e7a66..0c5f9ac08 100644
--- a/.github/ISSUE_TEMPLATE/new_release.md
+++ b/.github/ISSUE_TEMPLATE/new_release.md
@@ -14,12 +14,12 @@ For details, see [RELEASE.md](https://github.com/kubernetes-sigs/cluster-api-pro
- [ ] [When bumping `X` or `Y`] Add a new entry of new release branch to [depandabot.yml](https://github.com/kubernetes-sigs/cluster-api-provider-openstack/blob/main/.github/dependabot.yml).
- [ ] [When bumping `X` or `Y`] Add a new entry of new release branch to [security-scan.yaml](https://github.com/kubernetes-sigs/cluster-api-provider-openstack/blob/main/.github/workflows/security-scan.yaml).
- [ ] [When bumping `X` or `Y`] Add a new entry to [metadata.yaml](https://github.com/kubernetes-sigs/cluster-api-provider-openstack/blob/main/metadata.yaml)
- as [described in the CAPI book](https://cluster-api.sigs.k8s.io/clusterctl/provider-contract.html#metadata-yaml)
+ as [described in the CAPI book](https://cluster-api.sigs.k8s.io/developer/providers/contracts/clusterctl#metadata-yaml)
on the release branch prior to release.
-- [ ] Push tag to the repository.
+- [ ] Create the PR after generating release notes according to [RELEASE.md](https://github.com/kubernetes-sigs/cluster-api-provider-openstack/blob/main/RELEASE.md). Verify that the release PR looks good and make changes if necessary. When this PR is merged, release automation will push the tag to upstream and create a draft release notes.
- [ ] Promote the [staging image](https://console.cloud.google.com/cloud-build/builds?project=k8s-staging-capi-openstack) by
adding the new sha=>tag mapping to [images.yaml](https://github.com/kubernetes/k8s.io/blob/main/registry.k8s.io/images/k8s-staging-capi-openstack/images.yaml).
-- [ ] Verify that the new draft release looks good and make changes if necessary.
+- [ ] Verify that the new draft release looks good.
- [ ] Verify that the image was promoted sucessfully.
- [ ] Publish the release.
Mark the release as "latest" if it is the most recent minor release.
diff --git a/.golangci.yml b/.golangci.yml
index f2430a1e1..c0aa7e39d 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -2,136 +2,136 @@ version: "2"
run:
go: "1.24"
build-tags:
- - e2e
+ - e2e
allow-parallel-runners: true
linters:
default: none
enable:
- - asasalint
- - asciicheck
- - bidichk
- - bodyclose
- - copyloopvar
- - cyclop
- - dogsled
- - dupword
- - durationcheck
- - errcheck
- - forbidigo
- - goconst
- - gocritic
- - gocyclo
- - godot
- - goheader
- - gomodguard
- - goprintffuncname
- - gosec
- - govet
- - importas
- - ineffassign
- - makezero
- - misspell
- - nakedret
- - nestif
- - nilerr
- - noctx
- - nolintlint
- - prealloc
- - predeclared
- - revive
- - rowserrcheck
- - sqlclosecheck
- - staticcheck
- - thelper
- - unconvert
- - unparam
- - unused
- - wastedassign
- - whitespace
+ - asasalint
+ - asciicheck
+ - bidichk
+ - bodyclose
+ - copyloopvar
+ - cyclop
+ - dogsled
+ - dupword
+ - durationcheck
+ - errcheck
+ - forbidigo
+ - goconst
+ - gocritic
+ - gocyclo
+ - godot
+ - goheader
+ - gomodguard
+ - goprintffuncname
+ - gosec
+ - govet
+ - importas
+ - ineffassign
+ - makezero
+ - misspell
+ - nakedret
+ - nestif
+ - nilerr
+ - noctx
+ - nolintlint
+ - prealloc
+ - predeclared
+ - revive
+ - rowserrcheck
+ - sqlclosecheck
+ - staticcheck
+ - thelper
+ - unconvert
+ - unparam
+ - unused
+ - wastedassign
+ - whitespace
settings:
# TODO(sbuerin) fix remaining findings and set to 20 afterwards
cyclop:
max-complexity: 30
gocritic:
disabled-checks:
- - appendAssign
- - dupImport # https://github.com/go-critic/go-critic/issues/845
- - evalOrder
- - ifElseChain
- - octalLiteral
- - regexpSimplify
- - sloppyReassign
- - truncateCmp
- - typeDefFirst
- - unnamedResult
- - unnecessaryDefer
- - whyNoLint
- - wrapperFunc
- - rangeValCopy
- - hugeParam
+ - appendAssign
+ - dupImport # https://github.com/go-critic/go-critic/issues/845
+ - evalOrder
+ - ifElseChain
+ - octalLiteral
+ - regexpSimplify
+ - sloppyReassign
+ - truncateCmp
+ - typeDefFirst
+ - unnamedResult
+ - unnecessaryDefer
+ - whyNoLint
+ - wrapperFunc
+ - rangeValCopy
+ - hugeParam
enabled-tags:
- - diagnostic
- - experimental
- - performance
+ - diagnostic
+ - experimental
+ - performance
importas:
alias:
- # Kubernetes
- - pkg: k8s.io/api/core/v1
- alias: corev1
- - pkg: k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1
- alias: apiextensionsv1
- - pkg: k8s.io/apimachinery/pkg/apis/meta/v1
- alias: metav1
- - pkg: k8s.io/apimachinery/pkg/api/errors
- alias: apierrors
- - pkg: k8s.io/apimachinery/pkg/util/errors
- alias: kerrors
- # Controller Runtime
- - pkg: sigs.k8s.io/controller-runtime
- alias: ctrl
- # CAPO
- - pkg: sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha1
- alias: infrav1alpha1
- - pkg: sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1
- alias: infrav1
- - pkg: sigs.k8s.io/cluster-api-provider-openstack/pkg/utils/errors
- alias: capoerrors
- # CAPI
- - pkg: sigs.k8s.io/cluster-api/api/v1alpha3
- alias: clusterv1alpha3
- - pkg: sigs.k8s.io/cluster-api/api/v1alpha4
- alias: clusterv1alpha4
- - pkg: sigs.k8s.io/cluster-api/api/core/v1beta2
- alias: clusterv1
- - pkg: sigs.k8s.io/cluster-api/api/core/v1beta1
- alias: clusterv1beta1
- - pkg: sigs.k8s.io/cluster-api/util/deprecated/v1beta1/conditions
- alias: v1beta1conditions
- - pkg: sigs.k8s.io/cluster-api/util/deprecated/v1beta1/conditions/v1beta2
- alias: deprecatedv1beta2conditions
- - pkg: sigs.k8s.io/cluster-api/util/conditions/deprecated/v1beta1
- alias: deprecatedv1beta1conditions
- - pkg: sigs.k8s.io/cluster-api/util/deprecated/v1beta1/patch
- alias: v1beta1patch
- - pkg: sigs.k8s.io/cluster-api/api/ipam/v1beta2
- alias: ipamv1
- # CABPK
- - pkg: sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1alpha3
- alias: bootstrapv1alpha3
- - pkg: sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1alpha4
- alias: bootstrapv1alpha4
- - pkg: sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1
- alias: bootstrapv1
- # KCP
- - pkg: sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1alpha3
- alias: controlplanev1alpha3
- - pkg: sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1alpha4
- alias: controlplanev1alpha4
- - pkg: sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1
- alias: controlplanev1
- # ORC
- - pkg: github.com/k-orc/openstack-resource-controller/api/v1alpha1
- alias: orcv1alpha1
+ # Kubernetes
+ - pkg: k8s.io/api/core/v1
+ alias: corev1
+ - pkg: k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1
+ alias: apiextensionsv1
+ - pkg: k8s.io/apimachinery/pkg/apis/meta/v1
+ alias: metav1
+ - pkg: k8s.io/apimachinery/pkg/api/errors
+ alias: apierrors
+ - pkg: k8s.io/apimachinery/pkg/util/errors
+ alias: kerrors
+ # Controller Runtime
+ - pkg: sigs.k8s.io/controller-runtime
+ alias: ctrl
+ # CAPO
+ - pkg: sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha1
+ alias: infrav1alpha1
+ - pkg: sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1
+ alias: infrav1
+ - pkg: sigs.k8s.io/cluster-api-provider-openstack/pkg/utils/errors
+ alias: capoerrors
+ # CAPI
+ - pkg: sigs.k8s.io/cluster-api/api/v1alpha3
+ alias: clusterv1alpha3
+ - pkg: sigs.k8s.io/cluster-api/api/v1alpha4
+ alias: clusterv1alpha4
+ - pkg: sigs.k8s.io/cluster-api/api/core/v1beta2
+ alias: clusterv1
+ - pkg: sigs.k8s.io/cluster-api/api/core/v1beta1
+ alias: clusterv1beta1
+ - pkg: sigs.k8s.io/cluster-api/util/deprecated/v1beta1/conditions
+ alias: v1beta1conditions
+ - pkg: sigs.k8s.io/cluster-api/util/deprecated/v1beta1/conditions/v1beta2
+ alias: deprecatedv1beta2conditions
+ - pkg: sigs.k8s.io/cluster-api/util/conditions/deprecated/v1beta1
+ alias: deprecatedv1beta1conditions
+ - pkg: sigs.k8s.io/cluster-api/util/deprecated/v1beta1/patch
+ alias: v1beta1patch
+ - pkg: sigs.k8s.io/cluster-api/api/ipam/v1beta2
+ alias: ipamv1
+ # CABPK
+ - pkg: sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1alpha3
+ alias: bootstrapv1alpha3
+ - pkg: sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1alpha4
+ alias: bootstrapv1alpha4
+ - pkg: sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1
+ alias: bootstrapv1
+ # KCP
+ - pkg: sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1alpha3
+ alias: controlplanev1alpha3
+ - pkg: sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1alpha4
+ alias: controlplanev1alpha4
+ - pkg: sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1
+ alias: controlplanev1
+ # ORC
+ - pkg: github.com/k-orc/openstack-resource-controller/api/v1alpha1
+ alias: orcv1alpha1
no-unaliased: true
nestif:
# minimal complexity of if statements to report, 5 by default
@@ -144,73 +144,82 @@ linters:
exclusions:
generated: lax
presets:
- - comments
- - common-false-positives
- - legacy
- - std-error-handling
+ - comments
+ - common-false-positives
+ - legacy
+ - std-error-handling
# List of regexps of issue texts to exclude, empty list by default.
rules:
- - linters:
- - gosec
- text: 'G108: Profiling endpoint is automatically exposed on /debug/pprof'
- - linters:
- - gosec
- text: 'G108: Profiling endpoint is automatically exposed on /debug/pprof'
- # This directive allows the embed package to be imported with an underscore everywhere.
- - linters:
- - revive
- source: _ "embed"
- - linters:
- - revive
- - staticcheck
- path: (test)/.*.go
- text: should not use dot imports
- - linters:
- - revive
- path: test/e2e/shared/defaults.go
- text: 'exported: exported const .* should have comment \(or a comment on this block\) or be unexported'
- - linters:
- - revive
- text: 'var-naming: don''t use underscores in Go names;'
- - linters:
- - staticcheck
- path: (api\/.*|pkg/utils/optional)\/.*conversion.*\.go$
- text: 'ST1003: should not use underscores in Go names;'
- - linters:
- - staticcheck
- path: pkg/utils/conversioncommon/.*.go
- text: 'ST1003: should not use underscores in Go names;'
- - linters:
- - staticcheck
- text: 'SA1019: "sigs.k8s.io/cluster-api/api/core/v1beta1" is deprecated: This package is deprecated and is going to be removed when support for v1beta1 will be dropped.'
- - linters:
- - staticcheck
- text: 'SA1019: "sigs.k8s.io/cluster-api/util/deprecated/v1beta1/conditions" is deprecated: This package is deprecated and is going to be removed when support for v1beta1 will be dropped. Please see https://github.com/kubernetes-sigs/cluster-api/blob/main/docs/proposals/20240916-improve-status-in-CAPI-resources.md for more details.'
+ - linters:
+ - gosec
+ text: 'G108: Profiling endpoint is automatically exposed on /debug/pprof'
+ - linters:
+ - gosec
+ text: 'G108: Profiling endpoint is automatically exposed on /debug/pprof'
+ # This directive allows the embed package to be imported with an underscore everywhere.
+ - linters:
+ - revive
+ source: _ "embed"
+ - linters:
+ - revive
+ - staticcheck
+ path: (test)/.*.go
+ text: should not use dot imports
+ - linters:
+ - revive
+ path: test/e2e/shared/defaults.go
+ text: 'exported: exported const .* should have comment \(or a comment on this block\) or be unexported'
+ - linters:
+ - revive
+ text: 'var-naming: don''t use underscores in Go names;'
+ - linters:
+ - staticcheck
+ path: (api\/.*|pkg/utils/optional)\/.*conversion.*\.go$
+ text: 'ST1003: should not use underscores in Go names;'
+ - linters:
+ - staticcheck
+ path: pkg/utils/conversioncommon/.*.go
+ text: 'ST1003: should not use underscores in Go names;'
+ - linters:
+ - staticcheck
+ text: 'SA1019: "sigs.k8s.io/cluster-api/api/core/v1beta1" is deprecated: This package is deprecated and is going to be removed when support for v1beta1 will be dropped.'
+ - linters:
+ - staticcheck
+ text: 'SA1019: "sigs.k8s.io/cluster-api/util/deprecated/v1beta1/conditions" is deprecated: This package is deprecated and is going to be removed when support for v1beta1 will be dropped. Please see https://github.com/kubernetes-sigs/cluster-api/blob/main/docs/proposals/20240916-improve-status-in-CAPI-resources.md for more details.'
+ - linters:
+ - staticcheck
+ text: 'SA1019: .*.Status.Ready is deprecated: This field is deprecated and will be removed in a future API version. Use status.conditions to determine the ready state of the *'
+ - linters:
+ - staticcheck
+ text: 'SA1019: .*.Status.FailureReason is deprecated: This field is deprecated and will be removed in a future API version. Use status.conditions to report failures.'
+ - linters:
+ - staticcheck
+ text: 'SA1019: .*.Status.FailureMessage is deprecated: This field is deprecated and will be removed in a future API version. Use status.conditions to report failures.'
paths:
- - zz_generated.*\.go$
- - third_party$
- - builtin$
- - examples$
+ - zz_generated.*\.go$
+ - third_party$
+ - builtin$
+ - examples$
issues:
max-issues-per-linter: 0
max-same-issues: 0
formatters:
enable:
- - gci
- - gofmt
- - gofumpt
- - goimports
+ - gci
+ - gofmt
+ - gofumpt
+ - goimports
settings:
gci:
sections:
- - standard
- - default
- - prefix(github.com/k-orc/openstack-resource-controller)
- - prefix(sigs.k8s.io/cluster-api-provider-openstack)
+ - standard
+ - default
+ - prefix(github.com/k-orc/openstack-resource-controller)
+ - prefix(sigs.k8s.io/cluster-api-provider-openstack)
exclusions:
generated: lax
paths:
- - zz_generated.*\.go$
- - third_party$
- - builtin$
- - examples$
+ - zz_generated.*\.go$
+ - third_party$
+ - builtin$
+ - examples$
diff --git a/.yamllint.yaml b/.yamllint.yaml
new file mode 100644
index 000000000..ac6823ff3
--- /dev/null
+++ b/.yamllint.yaml
@@ -0,0 +1,14 @@
+yaml-files:
+- '*.yaml'
+- '*.yml'
+- '.yamllint'
+
+rules:
+ trailing-spaces: enable
+ key-duplicates: enable
+ indentation:
+ spaces: 2
+ indent-sequences: false # Enforce k8s-style indentation
+ check-multi-line-strings: false
+ truthy:
+ allowed-values: [ 'true', 'false', 'yes', 'no', 'on' ]
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 000000000..81ed5d117
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,285 @@
+# AGENTS.md - Agent Guidelines for Cluster API Provider OpenStack
+
+This document provides guidelines and useful commands for AI agents contributing to the Cluster API Provider OpenStack (CAPO) repository.
+
+> **⚠️ IMPORTANT**: When making changes to Makefile targets, PR requirements, code generation workflows, verification steps, or any other information referenced in this document, **AGENTS.md must be updated accordingly** to keep it synchronized with the actual project state.
+
+## Overview
+
+Cluster API Provider OpenStack (CAPO) is a Kubernetes-native declarative infrastructure provider for managing Kubernetes clusters on OpenStack. It implements the Cluster API (CAPI) specification for self-managed Kubernetes clusters on OpenStack infrastructure.
+
+**Key Concepts:**
+- **CAPI**: Cluster API - the upstream Kubernetes project defining cluster lifecycle APIs
+- **CAPO**: Cluster API Provider OpenStack - this repository
+- **Reconciler**: Controller-runtime pattern for managing Kubernetes custom resources
+- **Scope**: Context and configuration wrapper for controllers and services
+
+## Key Requirements for Contributors
+
+### Legal Requirements
+
+- **CLA Required**: All contributors MUST sign the Kubernetes Contributor License Agreement (CLA)
+- See: https://git.k8s.io/community/CLA.md
+
+### Pull Request Labels
+
+All code PRs MUST be labeled with one of:
+- ⚠️ `:warning:` - major or breaking changes
+- ✨ `:sparkles:` - feature additions
+- 🐛 `:bug:` - patch and bugfixes
+- 📖 `:book:` - documentation or proposals
+- 🌱 `:seedling:` - minor or other
+
+## Essential Make Targets
+
+### Code Quality & Verification
+
+```bash
+# Run all verification checks (should pass before submitting PR)
+make verify
+
+# Verify boilerplate headers
+make verify-boilerplate
+
+# Verify go modules are up to date
+make verify-modules
+
+# Verify generated code is up to date
+make verify-gen
+
+# Verify container images for vulnerabilities
+make verify-container-images
+
+# Check code for security vulnerabilities
+make verify-govulncheck
+
+# Run all security checks (images + code)
+make verify-security
+```
+
+### Linting
+
+```bash
+# Lint codebase
+make lint
+
+# Lint and auto-fix issues
+make lint-update
+
+# Run faster linters only
+make lint-fast
+```
+
+### Testing
+
+```bash
+# Run unit tests
+make test
+
+# Run unit tests for CAPO specifically
+make test-capo
+
+# Build e2e test binaries
+make build-e2e-tests
+
+# Run e2e tests (requires OpenStack environment)
+make test-e2e
+
+# Run conformance tests
+make test-conformance
+
+# Compile e2e tests (verify they build)
+make compile-e2e
+```
+
+### Code Generation
+
+```bash
+# Generate ALL code (manifests, deepcopy, clients, mocks, docs)
+make generate
+
+# Generate Go code (mocks, etc.)
+make generate-go
+
+# Generate controller-gen code (deepcopy, etc.)
+make generate-controller-gen
+
+# Generate client code (clientsets, listers, informers)
+make generate-codegen
+
+# Generate CRD manifests
+make generate-manifests
+
+# Generate API documentation
+make generate-api-docs
+
+# Generate cluster templates
+make templates
+```
+
+### Dependency Management
+
+```bash
+# Update go modules
+make modules
+
+# Check for API differences (useful before breaking changes)
+make apidiff
+```
+
+### Building
+
+```bash
+# Build manager binaries
+make managers
+
+# Build all binaries
+make binaries
+
+# Build Docker image
+make docker-build
+
+# Build debug Docker image
+make docker-build-debug
+
+# Build for all architectures
+make docker-build-all
+```
+
+### Cleanup
+
+```bash
+# Clean all build artifacts
+make clean
+
+# Clean binaries only
+make clean-bin
+
+# Clean temporary files
+make clean-temporary
+
+# Clean release artifacts
+make clean-release
+```
+
+## Important Development Patterns
+
+### Adding New OpenStack Resources
+
+1. Define API types in `/api/v1beta1` (or `/api/v1alpha1` for experimental features)
+2. Run `make generate` to create deepcopy methods and update CRDs
+3. Create controller in `/controllers`
+4. Create service implementation in `/pkg/cloud/services/`
+5. Update or create scope in `/pkg/scope` if needed
+6. Add webhooks in `/pkg/webhooks` for validation/defaulting
+7. Add unit tests for controller and services
+8. Update documentation
+9. Generate cluster templates if applicable with `make templates`
+
+### Testing Strategy
+
+1. **Unit Tests**: Test individual functions/methods with mocks
+2. **Integration Tests**: Test controller behavior with envtest
+3. **E2E Tests**: Deploy real clusters on OpenStack, verify functionality
+4. **Conformance Tests**: Run upstream Kubernetes conformance suite
+
+## Pre-Submit Checklist for Agents
+
+Before submitting a PR, ensure:
+
+1. **Code is generated and up to date**:
+ ```bash
+ make generate
+ ```
+
+2. **Modules are tidy**:
+ ```bash
+ make modules
+ ```
+
+3. **Code passes linting**:
+ ```bash
+ make lint
+ ```
+
+4. **Tests pass**:
+ ```bash
+ make test
+ ```
+
+5. **All verification checks pass**:
+ ```bash
+ make verify
+ ```
+
+## Common Workflows
+
+### Making Code Changes
+
+1. Make your code changes
+2. Run code generation: `make generate`
+3. Update modules if needed: `make modules`
+4. Run tests: `make test`
+5. Lint the code: `make lint`
+6. Verify everything: `make verify`
+7. Commit changes with descriptive message
+
+### Updating Dependencies
+
+1. Update `go.mod` or `hack/tools/go.mod` as needed
+2. Run: `make modules`
+3. Run: `make verify-modules`
+4. Test that everything still works: `make test`
+
+## Common Issues
+
+### Linting Errors
+
+The project uses golangci-lint. If you get lint errors:
+1. Run `make lint-update` first to auto-fix
+2. Check `.golangci.yml` for enabled linters
+3. Some issues require manual fixes (cognitive complexity, error handling, etc.)
+4. Don't disable linters without good reason - fix the underlying issue
+
+### Test Failures
+
+- **envtest issues**: Ensure KUBEBUILDER_ASSETS is set correctly
+- **Flaky E2E tests**: Transient infrastructure issues, failure to deploy devstack
+
+### Generated File Drift
+
+If `make verify` fails with generated file drift:
+1. Run `make generate` to regenerate all files
+2. Review the changes to ensure they're expected
+3. Commit the generated files
+4. Never manually edit generated files
+
+## Documentation
+
+Primary documentation is in `/docs/book/src/` (mdBook format):
+- Getting started guides
+- Developer documentation
+- Troubleshooting guides
+- API reference
+- Cluster template documentation
+
+Build and serve docs locally:
+```bash
+make -C docs/book serve
+```
+
+## Quick Reference
+
+| Task | Command |
+|------|---------|
+| Full verification before PR | `make verify && make test` |
+| Generate all code | `make generate` |
+| Update dependencies | `make modules` |
+| Lint and fix | `make lint-update` |
+| Run tests | `make test` |
+| Build binary | `make managers` |
+| Build Docker image | `make docker-build` |
+| Clean everything | `make clean` |
+| Check API compatibility | `make apidiff` |
+| Generate templates | `make templates` |
+| Build and serve docs | `make -C docs/book serve` |
diff --git a/Dockerfile b/Dockerfile
index f6b4f2a15..1c2f977b6 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -14,7 +14,7 @@
# Build the manager binary
ARG GO_VERSION
-FROM golang:${GO_VERSION:-1.24.9} AS builder
+FROM golang:${GO_VERSION:-1.24.11} AS builder
WORKDIR /workspace
# Run this with docker build --build_arg goproxy=$(go env GOPROXY) to override the goproxy
diff --git a/Makefile b/Makefile
index 6ccff05ab..1a1ed68e6 100644
--- a/Makefile
+++ b/Makefile
@@ -27,7 +27,7 @@ unexport GOPATH
TRACE ?= 0
# Go
-GO_VERSION ?= 1.24.9
+GO_VERSION ?= 1.24.11
# Directories.
ARTIFACTS ?= $(REPO_ROOT)/_artifacts
@@ -188,6 +188,7 @@ e2e-templates: $(addprefix $(E2E_NO_ARTIFACT_TEMPLATES_DIR)/, \
cluster-template-multi-network.yaml \
cluster-template-without-lb.yaml \
cluster-template.yaml \
+ cluster-template-topology.yaml \
cluster-template-flatcar.yaml \
cluster-template-k8s-upgrade.yaml \
cluster-template-flatcar-sysext.yaml \
@@ -448,14 +449,14 @@ staging-manifests:
## --------------------------------------
##@ Release
## --------------------------------------
-
ifneq (,$(findstring -,$(RELEASE_TAG)))
PRE_RELEASE=true
endif
-PREVIOUS_TAG ?= $(shell git tag -l | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$$" | sort -V | grep -B1 $(RELEASE_TAG) | head -n 1 2>/dev/null)
+# List all tags, add the new tag to the list, sort and pick the previous one.
+PREVIOUS_TAG ?= $(shell (git tag -l | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$$"; echo "$(RELEASE_TAG)") | sort -V | grep -B1 "^$(RELEASE_TAG)$$" | grep -v "^$(RELEASE_TAG)$$" | head -n 1 2>/dev/null)
## set by Prow, ref name of the base branch, e.g., main
RELEASE_DIR := out
-RELEASE_NOTES_DIR := _releasenotes
+RELEASE_NOTES_DIR := releasenotes
.PHONY: $(RELEASE_DIR)
$(RELEASE_DIR):
@@ -478,7 +479,7 @@ list-image:
gcloud container images list-tags $(STAGING_REGISTRY)/$(IMAGE) --filter="tags=('$(RELEASE_TAG)')" --format=json
.PHONY: release
-release: $(RELEASE_NOTES) clean-release $(RELEASE_DIR) ## Builds and push container images using the latest git tag for the commit.
+release: $(RELEASE_NOTES) $(RELEASE_DIR) ## Builds and push container images using the latest git tag for the commit.
@if [ -z "${RELEASE_TAG}" ]; then echo "RELEASE_TAG is not set"; exit 1; fi
@if ! [ -z "$$(git status --porcelain)" ]; then echo "Your local git repository contains uncommitted changes, use git clean before proceeding."; fi
git checkout "${RELEASE_TAG}"
@@ -486,6 +487,7 @@ release: $(RELEASE_NOTES) clean-release $(RELEASE_DIR) ## Builds and push conta
$(MAKE) manifest-modification REGISTRY=$(PROD_REGISTRY)
$(MAKE) release-manifests
$(MAKE) release-templates
+ $(MAKE) generate-release-notes
.PHONY: manifest-modification
manifest-modification: # Set the manifest images to the staging/production bucket.
@@ -551,7 +553,7 @@ generate-release-notes: $(RELEASE_NOTES_DIR) $(RELEASE_NOTES)
fi
"$(RELEASE_NOTES)" --repository=kubernetes-sigs/cluster-api-provider-openstack \
--prefix-area-label=false --add-kubernetes-version-support=false \
- --from=$(PREVIOUS_TAG) --release=$(RELEASE_TAG) >> $(RELEASE_NOTES_DIR)/$(RELEASE_TAG).md
+ --from=tags/$(PREVIOUS_TAG) --release=$(RELEASE_TAG) >> $(RELEASE_NOTES_DIR)/$(RELEASE_TAG).md
.PHONY: templates
templates: ## Generate cluster templates
diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES
index 7f32d2e48..d5c432278 100644
--- a/OWNERS_ALIASES
+++ b/OWNERS_ALIASES
@@ -22,4 +22,5 @@ aliases:
- emilienm
- lentzi90
cluster-api-openstack-reviewers:
+ - bnallapeta
- smoshiur1237
diff --git a/RELEASE.md b/RELEASE.md
index 8b7ea9f06..668567573 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -19,6 +19,7 @@
A release version string is: `vX.Y.Z`.
A pre-release version string additionally has a suffix:
+
- `alpha` for an alpha release
- `beta` for a beta release
- `rc` for a release candidate
@@ -41,40 +42,75 @@ The content of the release notes differs depending on the type of release, speci
There is an [issue template](.github/ISSUE_TEMPLATE/new_release.md) to help track release activities.
1. Make sure your repo is clean by git's standards. It is recommended to use a fresh checkout.
-1. When bumping `X` or `Y` (but not Z or the pre-release suffix) in the release version you must create a new release branch called `release-X.Y`.
- > NOTE: `upstream` should be the name of the remote pointing to `github.com/kubernetes-sigs/cluster-api-provider-openstack`
- - `git checkout main`
- - `git pull`
- - `git checkout -b release-X.Y`
- - `git push --set-upstream upstream`
+1. Repository Setup
+ - Clone the repository: `git clone git@github.com:kubernetes-sigs/cluster-api-provider-openstack.git`
+ or if using existing repository, make sure origin is set to the fork and
+ upstream is set to `kubernetes-sigs`. Verify if your remote is set properly or not
+ by using following command `git remote -v`, where origin points to fork and upstream points to main repo.
+ - Fetch the remote (`kubernetes-sigs`): `git fetch upstream`
+ This makes sure that all the tags are accessible.
+
1. When bumping `X` or `Y` (but not Z or the pre-release suffix) in the release version, ensure you have added a new
entry to [metadata.yaml](https://github.com/kubernetes-sigs/cluster-api-provider-openstack/blob/main/metadata.yaml)
- as [described in the CAPI book](https://cluster-api.sigs.k8s.io/clusterctl/provider-contract.html#metadata-yaml), and
+ as [described in the CAPI book](https://cluster-api.sigs.k8s.io/developer/providers/contracts/clusterctl#metadata-yaml), and
that this has been committed to the release branch prior to release.
-1. Make sure you are on the correct release branch: `release-X.Y`
-1. Set an environment variable with the version, e.g.:
- - `VERSION=v0.6.0`
-1. Create an annotated tag
- - `git tag -s -a $VERSION -m $VERSION`.
-1. Push the tag to the GitHub repository:
- > NOTE: `upstream` should be the name of the remote pointing to `github.com/kubernetes-sigs/cluster-api-provider-openstack`
- - `git push upstream $VERSION`
-
- This will cause the image to be automatically built by CI and pushed to the staging repository. As this only builds
- the image, it only takes a few minutes.
- It also triggers the [release](https://github.com/kubernetes-sigs/cluster-api-provider-openstack/blob/main/.github/workflows/release.yaml) workflow which will generate release notes and artifacts, and create a draft release in GitHub.
-1. Follow the [image promotion process](https://github.com/kubernetes/k8s.io/blob/main/registry.k8s.io/README.md#image-promoter) to promote the image from the staging repo to `registry.k8s.io/capi-openstack`.
- The staging repository can be inspected at https://console.cloud.google.com/gcr/images/k8s-staging-capi-openstack/GLOBAL. Be
+
+1. Creating Release Notes
+ - Switch to the main branch: `git checkout main`
+ - Create a new branch for the release notes**:
+ `git checkout -b release-notes-X.Y.Z origin/main`
+ - Generate the release notes: `RELEASE_TAG=vX.Y.Z make generate-release-notes`
+ - Replace `vX.Y.Z` with the new release tag you're creating.
+ - This command generates the release notes here
+ `releasenotes/.md` .
+
+1. Next step is to clean up the release note manually.
+ - If release is not an alpha or a beta or release candidate, check for duplicates,
+ reverts, and incorrect classifications of PRs, and whatever release
+ creation tagged to be manually checked.
+ - For any superseded PRs (like same dependency uplifted multiple times, or
+ commit revertion) that provide no value to the release, move them to
+ Superseded section. This way the changes are acknowledged to be part of the
+ release, but not overwhelming the important changes contained in the release.
+ - Commit your changes, push the new branch and create a pull request:
+ - The commit and PR title should be 🚀 Release v1.x.y:
+ -`git commit -S -s -m ":rocket: Release vX.Y.Z"`
+ -`git push -u origin release-notes-X.Y.Z`
+ - Important! The commit should only contain the release notes file, nothing
+ else, otherwise automation will not work. Push as normal, through your fork (`origin`).
+ - Ask maintainers and release team members to review your pull request.
+
+ Once the PR is merged, the following GitHub actions are triggered:
+
+ - GitHub action `Create Release` runs following jobs
+ - GitHub job `push_release_tags` will create and push the tags. This action
+ will also create release branch if its missing and release is `rc` or minor.
+ - GitHub job `create draft release` creates draft release. Don't publish the
+ release yet. Running actions are visible on the
+ [Actions](https://github.com/kubernetes-sigs/cluster-api-provider-openstack/actions)
+ page, and draft release will be visible on top of the
+ [Releases](https://github.com/kubernetes-sigs/cluster-api-provider-openstack/releases).
+
+ The image will also be automatically built by CI and pushed to the staging repository. As this only builds the image, it only takes a few minutes.
+
+1. Follow the [image promotion process](https://github.com/kubernetes/k8s.io/blob/main/registry.k8s.io/README.md#image-promoter)
+ to promote the image from the staging repo to `registry.k8s.io/capi-openstack`.
+ The staging repository can be inspected at [Staging CAPI Openstack](https://console.cloud.google.com/gcr/images/k8s-staging-capi-openstack/GLOBAL). Be
sure to choose the top level `capi-openstack-controller`, which will provide the multi-arch manifest, rather than one for a specific architecture.
The image build logs are available at [Cloud Build](https://console.cloud.google.com/cloud-build/builds?project=k8s-staging-capi-openstack).
Add the new sha=>tag mapping to the [images.yaml](https://github.com/kubernetes/k8s.io/blob/main/registry.k8s.io/images/k8s-staging-capi-openstack/images.yaml) (use the sha of the image with the corresponding tag). The PR to update the [images.yaml](https://github.com/kubernetes/k8s.io/blob/main/registry.k8s.io/images/k8s-staging-capi-openstack/images.yaml) must be approved in the [OWNERS](https://github.com/kubernetes/k8s.io/blob/main/registry.k8s.io/images/k8s-staging-capi-openstack/OWNERS) file and merged.
+ Here is an example [pull request](https://github.com/kubernetes/k8s.io/pull/8807).
+
+ It is good practise to get somebody else to review this PR. It is safe to perform the following steps while waiting for review and the promotion of the image.
- It is good practise to get somebody else to review this PR. It is safe to perform the following steps while waiting
- for review and the promotion of the image.
1. Check carefully the [draft release](https://github.com/kubernetes-sigs/cluster-api-provider-openstack/releases)
created by the workflow. Ensure that the release notes are correct and that the artifacts are present.
If any changes are needed, edit the release notes in the GitHub UI and add any missing artifacts.
1. Ensure that the release image has been promoted.
+1. If the release you're making is not a new major release, new minor release,
+ or a new patch release from the latest release branch, uncheck the box for
+ latest release. If it is a release candidate (RC) or a beta or an alpha
+ release, tick pre-release box.
1. Publish release.
### Post release actions
@@ -89,12 +125,16 @@ There is an [issue template](.github/ISSUE_TEMPLATE/new_release.md) to help trac
Releasing requires a particular set of permissions.
-* Approver role for the image promoter process ([kubernetes/k8s.io/blob/main/registry.k8s.io/images/k8s-staging-capi-openstack/OWNERS](https://github.com/kubernetes/k8s.io/blob/main/registry.k8s.io/images/k8s-staging-capi-openstack/OWNERS))
-* Tag push and release creation rights to the GitHub repository (team `cluster-api-provider-openstack-maintainers` in [kubernetes/org/config/kubernetes-sigs/sig-cluster-lifecycle/teams.yaml](https://github.com/kubernetes/org/blob/main/config/kubernetes-sigs/sig-cluster-lifecycle/teams.yaml))
+1. Approver role for the image promoter process ([kubernetes/k8s.io/blob/main/registry.k8s.io/images/k8s-staging-capi-openstack/OWNERS](https://github.com/kubernetes/k8s.io/blob/main/registry.k8s.io/images/k8s-staging-capi-openstack/OWNERS))
+
+1. Tag push and release creation rights to the GitHub repository (team `cluster-api-provider-openstack-maintainers` in [kubernetes/org/config/kubernetes-sigs/sig-cluster-lifecycle/teams.yaml](https://github.com/kubernetes/org/blob/main/config/kubernetes-sigs/sig-cluster-lifecycle/teams.yaml))
## Staging
There is a post-submit Prow job running after each commit on `main` which pushes a new image to the staging repo (`gcr.io/k8s-staging-capi-openstack/capi-openstack-controller:latest`). Following configuration is involved:
-* staging gcr bucket: [kubernetes/k8s.io/blob/main/registry.k8s.io/manifests/k8s-staging-capi-openstack/promoter-manifest.yaml](https://github.com/kubernetes/k8s.io/blob/main/registry.k8s.io/manifests/k8s-staging-capi-openstack/promoter-manifest.yaml)
-* post-submit `post-capi-openstack-push-images` Prow job: [kubernetes/test-infra/blob/master/config/jobs/image-pushing/k8s-staging-cluster-api.yaml](https://github.com/kubernetes/test-infra/blob/master/config/jobs/image-pushing/k8s-staging-cluster-api.yaml)) (corresponding dashboard is located at [https://testgrid.k8s.io/sig-cluster-lifecycle-image-pushes#post-capi-openstack-push-images](https://testgrid.k8s.io/sig-cluster-lifecycle-image-pushes#post-capi-openstack-push-images))
-* Google Cloud Build configuration which is used by the Prow job: [kubernetes-sigs/cluster-api-provider-openstack/cloudbuild.yaml](https://github.com/kubernetes-sigs/cluster-api-provider-openstack/blob/main/cloudbuild.yaml)
+
+1. staging gcr bucket: [kubernetes/k8s.io/blob/main/registry.k8s.io/manifests/k8s-staging-capi-openstack/promoter-manifest.yaml](https://github.com/kubernetes/k8s.io/blob/main/registry.k8s.io/manifests/k8s-staging-capi-openstack/promoter-manifest.yaml)
+
+1. post-submit `post-capi-openstack-push-images` Prow job: [kubernetes/test-infra/blob/master/config/jobs/image-pushing/k8s-staging-cluster-api.yaml](https://github.com/kubernetes/test-infra/blob/master/config/jobs/image-pushing/k8s-staging-cluster-api.yaml) (corresponding dashboard is located at [https://testgrid.k8s.io/sig-cluster-lifecycle-image-pushes#post-capi-openstack-push-images](https://testgrid.k8s.io/sig-cluster-lifecycle-image-pushes#post-capi-openstack-push-images))
+
+1. Google Cloud Build configuration which is used by the Prow job: [kubernetes-sigs/cluster-api-provider-openstack/cloudbuild.yaml](https://github.com/kubernetes-sigs/cluster-api-provider-openstack/blob/main/cloudbuild.yaml)
diff --git a/api/v1beta1/conditions_consts.go b/api/v1beta1/conditions_consts.go
index d84500772..86dcc086f 100644
--- a/api/v1beta1/conditions_consts.go
+++ b/api/v1beta1/conditions_consts.go
@@ -69,3 +69,32 @@ const (
// UnableToFindFloatingIPNetworkReason is used when the floating ip network is not found.
UnableToFindFloatingIPNetworkReason = "UnableToFindFloatingIPNetwork"
)
+
+const (
+ // NetworkReadyCondition reports on the current status of the cluster network infrastructure.
+ // Ready indicates that the network, subnets, and related resources have been successfully provisioned.
+ NetworkReadyCondition clusterv1beta1.ConditionType = "NetworkReady"
+
+ // RouterReadyCondition reports on the current status of the cluster router infrastructure.
+ // Ready indicates that the router and its interfaces have been successfully provisioned.
+ RouterReadyCondition clusterv1beta1.ConditionType = "RouterReady"
+
+ // SecurityGroupsReadyCondition reports on the current status of the cluster security groups.
+ // Ready indicates that all required security groups have been successfully provisioned.
+ SecurityGroupsReadyCondition clusterv1beta1.ConditionType = "SecurityGroupsReady"
+
+ // APIEndpointReadyCondition reports on the current status of the cluster API endpoint.
+ // Ready indicates that the control plane endpoint has been successfully configured.
+ APIEndpointReadyCondition clusterv1beta1.ConditionType = "APIEndpointReady"
+
+ // NetworkReconcileFailedReason is used when network reconciliation fails.
+ NetworkReconcileFailedReason = "NetworkCreateFailed"
+ // SubnetReconcileFailedReason is used when subnet reconciliation fails.
+ SubnetReconcileFailedReason = "SubnetCreateFailed"
+ // RouterReconcileFailedReason is used when router reconciliation fails.
+ RouterReconcileFailedReason = "RouterCreateFailed"
+ // SecurityGroupReconcileFailedReason is used when security group reconciliation fails.
+ SecurityGroupReconcileFailedReason = "SecurityGroupCreateFailed"
+ // APIEndpointConfigFailedReason is used when API endpoint configuration fails.
+ APIEndpointConfigFailedReason = "APIEndpointConfigFailed"
+)
diff --git a/api/v1beta1/openstackcluster_types.go b/api/v1beta1/openstackcluster_types.go
index 02833b1ea..3c35efcf3 100644
--- a/api/v1beta1/openstackcluster_types.go
+++ b/api/v1beta1/openstackcluster_types.go
@@ -196,12 +196,27 @@ type OpenStackClusterSpec struct {
IdentityRef OpenStackIdentityReference `json:"identityRef"`
}
+// ClusterInitialization represents the initialization status of the cluster.
+type ClusterInitialization struct {
+ // Provisioned is set to true when the initial provisioning of the cluster infrastructure is completed.
+ // The value of this field is never updated after provisioning is completed.
+ // +optional
+ Provisioned bool `json:"provisioned,omitempty"`
+}
+
// OpenStackClusterStatus defines the observed state of OpenStackCluster.
type OpenStackClusterStatus struct {
// Ready is true when the cluster infrastructure is ready.
+ //
+ // Deprecated: This field is deprecated and will be removed in a future API version.
+ // Use status.conditions to determine the ready state of the cluster.
// +kubebuilder:default=false
Ready bool `json:"ready"`
+ // Initialization contains information about the initialization status of the cluster.
+ // +optional
+ Initialization *ClusterInitialization `json:"initialization,omitempty"`
+
// Network contains information about the created OpenStack Network.
// +optional
Network *NetworkStatusWithSubnets `json:"network,omitempty"`
@@ -257,6 +272,9 @@ type OpenStackClusterStatus struct {
// Any transient errors that occur during the reconciliation of
// OpenStackClusters can be added as events to the OpenStackCluster object
// and/or logged in the controller's output.
+ //
+ // Deprecated: This field is deprecated and will be removed in a future API version.
+ // Use status.conditions to report failures.
// +optional
FailureReason *capoerrors.DeprecatedCAPIClusterStatusError `json:"failureReason,omitempty"`
@@ -276,8 +294,18 @@ type OpenStackClusterStatus struct {
// Any transient errors that occur during the reconciliation of
// OpenStackClusters can be added as events to the OpenStackCluster object
// and/or logged in the controller's output.
+ //
+ // Deprecated: This field is deprecated and will be removed in a future API version.
+ // Use status.conditions to report failures.
// +optional
FailureMessage *string `json:"failureMessage,omitempty"`
+
+ // Conditions defines current service state of the OpenStackCluster.
+ // This field surfaces into Cluster's status.conditions[InfrastructureReady] condition.
+ // The Ready condition must surface issues during the entire lifecycle of the OpenStackCluster
+ // (both during initial provisioning and after the initial provisioning is completed).
+ // +optional
+ Conditions clusterv1beta1.Conditions `json:"conditions,omitempty"`
}
// +genclient
@@ -344,6 +372,16 @@ type ManagedSecurityGroups struct {
var _ IdentityRefProvider = &OpenStackCluster{}
+// GetConditions returns the observations of the operational state of the OpenStackCluster resource.
+func (c *OpenStackCluster) GetConditions() clusterv1beta1.Conditions {
+ return c.Status.Conditions
+}
+
+// SetConditions sets the underlying service state of the OpenStackCluster to the predescribed clusterv1.Conditions.
+func (c *OpenStackCluster) SetConditions(conditions clusterv1beta1.Conditions) {
+ c.Status.Conditions = conditions
+}
+
// GetIdentifyRef returns the cluster's namespace and IdentityRef.
func (c *OpenStackCluster) GetIdentityRef() (*string, *OpenStackIdentityReference) {
return &c.Namespace, &c.Spec.IdentityRef
diff --git a/api/v1beta1/openstackmachine_types.go b/api/v1beta1/openstackmachine_types.go
index 8b52e62a4..615a47f73 100644
--- a/api/v1beta1/openstackmachine_types.go
+++ b/api/v1beta1/openstackmachine_types.go
@@ -185,12 +185,27 @@ type ServerMetadata struct {
Value string `json:"value"`
}
+// MachineInitialization contains information about the initialization status of the machine.
+type MachineInitialization struct {
+ // Provisioned is set to true when the initial provisioning of the machine infrastructure is completed.
+ // The value of this field is never updated after provisioning is completed.
+ // +optional
+ Provisioned bool `json:"provisioned,omitempty"`
+}
+
// OpenStackMachineStatus defines the observed state of OpenStackMachine.
type OpenStackMachineStatus struct {
// Ready is true when the provider resource is ready.
+ //
+ // Deprecated: This field is deprecated and will be removed in a future API version.
+ // Use status.conditions to determine the ready state of the machine.
// +optional
Ready bool `json:"ready"`
+ // Initialization contains information about the initialization status of the machine.
+ // +optional
+ Initialization *MachineInitialization `json:"initialization,omitempty"`
+
// InstanceID is the OpenStack instance ID for this machine.
// +optional
InstanceID optional.String `json:"instanceID,omitempty"`
@@ -213,6 +228,11 @@ type OpenStackMachineStatus struct {
// +optional
Resources *MachineResources `json:"resources,omitempty"`
+ // FailureReason explains the reson behind a failure.
+ //
+ // Deprecated: This field is deprecated and will be removed in a future API version.
+ // Use status.conditions to report failures.
+ // +optional
FailureReason *capoerrors.DeprecatedCAPIMachineStatusError `json:"failureReason,omitempty"`
// FailureMessage will be set in the event that there is a terminal problem
@@ -231,9 +251,17 @@ type OpenStackMachineStatus struct {
// Any transient errors that occur during the reconciliation of Machines
// can be added as events to the Machine object and/or logged in the
// controller's output.
+ //
+ // Deprecated: This field is deprecated and will be removed in a future API version.
+ // Use status.conditions to report failures.
// +optional
FailureMessage *string `json:"failureMessage,omitempty"`
+ // Conditions defines current service state of the OpenStackMachine.
+ // This field surfaces into Machine's status.conditions[InfrastructureReady] condition.
+ // The Ready condition must surface issues during the entire lifecycle of the OpenStackMachine
+ // (both during initial provisioning and after the initial provisioning is completed).
+ // +optional
Conditions clusterv1beta1.Conditions `json:"conditions,omitempty"`
}
diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go
index b93422b7a..87510b42a 100644
--- a/api/v1beta1/zz_generated.deepcopy.go
+++ b/api/v1beta1/zz_generated.deepcopy.go
@@ -280,6 +280,21 @@ func (in *BlockDeviceVolume) DeepCopy() *BlockDeviceVolume {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ClusterInitialization) DeepCopyInto(out *ClusterInitialization) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterInitialization.
+func (in *ClusterInitialization) DeepCopy() *ClusterInitialization {
+ if in == nil {
+ return nil
+ }
+ out := new(ClusterInitialization)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExternalRouterIPParam) DeepCopyInto(out *ExternalRouterIPParam) {
*out = *in
@@ -441,6 +456,21 @@ func (in *LoadBalancer) DeepCopy() *LoadBalancer {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *MachineInitialization) DeepCopyInto(out *MachineInitialization) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineInitialization.
+func (in *MachineInitialization) DeepCopy() *MachineInitialization {
+ if in == nil {
+ return nil
+ }
+ out := new(MachineInitialization)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MachineResources) DeepCopyInto(out *MachineResources) {
*out = *in
@@ -765,6 +795,11 @@ func (in *OpenStackClusterSpec) DeepCopy() *OpenStackClusterSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OpenStackClusterStatus) DeepCopyInto(out *OpenStackClusterStatus) {
*out = *in
+ if in.Initialization != nil {
+ in, out := &in.Initialization, &out.Initialization
+ *out = new(ClusterInitialization)
+ **out = **in
+ }
if in.Network != nil {
in, out := &in.Network, &out.Network
*out = new(NetworkStatusWithSubnets)
@@ -822,6 +857,13 @@ func (in *OpenStackClusterStatus) DeepCopyInto(out *OpenStackClusterStatus) {
*out = new(string)
**out = **in
}
+ if in.Conditions != nil {
+ in, out := &in.Conditions, &out.Conditions
+ *out = make(corev1beta1.Conditions, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenStackClusterStatus.
@@ -1095,6 +1137,11 @@ func (in *OpenStackMachineSpec) DeepCopy() *OpenStackMachineSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OpenStackMachineStatus) DeepCopyInto(out *OpenStackMachineStatus) {
*out = *in
+ if in.Initialization != nil {
+ in, out := &in.Initialization, &out.Initialization
+ *out = new(MachineInitialization)
+ **out = **in
+ }
if in.InstanceID != nil {
in, out := &in.InstanceID, &out.InstanceID
*out = new(string)
diff --git a/cloudbuild-nightly.yaml b/cloudbuild-nightly.yaml
index 1fef4a3b1..a4f379598 100644
--- a/cloudbuild-nightly.yaml
+++ b/cloudbuild-nightly.yaml
@@ -4,15 +4,15 @@ options:
substitution_option: ALLOW_LOOSE
machineType: 'N1_HIGHCPU_8'
steps:
- - name: 'gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:v20250513-9264efb079'
- entrypoint: make
- env:
- - DOCKER_CLI_EXPERIMENTAL=enabled
- - TAG=$_GIT_TAG
- - PULL_BASE_REF=$_PULL_BASE_REF
- - DOCKER_BUILDKIT=1
- args:
- - release-staging-nightly
+- name: 'gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:v20250513-9264efb079'
+ entrypoint: make
+ env:
+ - DOCKER_CLI_EXPERIMENTAL=enabled
+ - TAG=$_GIT_TAG
+ - PULL_BASE_REF=$_PULL_BASE_REF
+ - DOCKER_BUILDKIT=1
+ args:
+ - release-staging-nightly
substitutions:
# _GIT_TAG will be filled with a git-based tag for the image, of the form vYYYYMMDD-hash, and
# can be used as a substitution
diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go
index a6c2ea433..757e866c1 100644
--- a/cmd/models-schema/zz_generated.openapi.go
+++ b/cmd/models-schema/zz_generated.openapi.go
@@ -339,12 +339,14 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.BindingProfile": schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_BindingProfile(ref),
"sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.BlockDeviceStorage": schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_BlockDeviceStorage(ref),
"sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.BlockDeviceVolume": schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_BlockDeviceVolume(ref),
+ "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.ClusterInitialization": schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_ClusterInitialization(ref),
"sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.ExternalRouterIPParam": schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_ExternalRouterIPParam(ref),
"sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.FilterByNeutronTags": schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_FilterByNeutronTags(ref),
"sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.FixedIP": schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_FixedIP(ref),
"sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.ImageFilter": schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_ImageFilter(ref),
"sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.ImageParam": schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_ImageParam(ref),
"sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.LoadBalancer": schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_LoadBalancer(ref),
+ "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.MachineInitialization": schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_MachineInitialization(ref),
"sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.MachineResources": schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_MachineResources(ref),
"sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.ManagedSecurityGroups": schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_ManagedSecurityGroups(ref),
"sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.NetworkFilter": schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_NetworkFilter(ref),
@@ -18054,6 +18056,26 @@ func schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_BlockDeviceVolu
}
}
+func schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_ClusterInitialization(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "ClusterInitialization represents the initialization status of the cluster.",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "provisioned": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Provisioned is set to true when the initial provisioning of the cluster infrastructure is completed. The value of this field is never updated after provisioning is completed.",
+ Type: []string{"boolean"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
func schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_ExternalRouterIPParam(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -18354,6 +18376,26 @@ func schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_LoadBalancer(re
}
}
+func schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_MachineInitialization(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "MachineInitialization contains information about the initialization status of the machine.",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "provisioned": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Provisioned is set to true when the initial provisioning of the machine infrastructure is completed. The value of this field is never updated after provisioning is completed.",
+ Type: []string{"boolean"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
func schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_MachineResources(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -19040,12 +19082,18 @@ func schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_OpenStackCluste
Properties: map[string]spec.Schema{
"ready": {
SchemaProps: spec.SchemaProps{
- Description: "Ready is true when the cluster infrastructure is ready.",
+ Description: "Ready is true when the cluster infrastructure is ready.\n\nDeprecated: This field is deprecated and will be removed in a future API version. Use status.conditions to determine the ready state of the cluster.",
Default: false,
Type: []string{"boolean"},
Format: "",
},
},
+ "initialization": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Initialization contains information about the initialization status of the cluster.",
+ Ref: ref("sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.ClusterInitialization"),
+ },
+ },
"network": {
SchemaProps: spec.SchemaProps{
Description: "Network contains information about the created OpenStack Network.",
@@ -19111,24 +19159,38 @@ func schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_OpenStackCluste
},
"failureReason": {
SchemaProps: spec.SchemaProps{
- Description: "FailureReason will be set in the event that there is a terminal problem reconciling the OpenStackCluster and will contain a succinct value suitable for machine interpretation.\n\nThis field should not be set for transitive errors that a controller faces that are expected to be fixed automatically over time (like service outages), but instead indicate that something is fundamentally wrong with the OpenStackCluster's spec or the configuration of the controller, and that manual intervention is required. Examples of terminal errors would be invalid combinations of settings in the spec, values that are unsupported by the controller, or the responsible controller itself being critically misconfigured.\n\nAny transient errors that occur during the reconciliation of OpenStackClusters can be added as events to the OpenStackCluster object and/or logged in the controller's output.",
+ Description: "FailureReason will be set in the event that there is a terminal problem reconciling the OpenStackCluster and will contain a succinct value suitable for machine interpretation.\n\nThis field should not be set for transitive errors that a controller faces that are expected to be fixed automatically over time (like service outages), but instead indicate that something is fundamentally wrong with the OpenStackCluster's spec or the configuration of the controller, and that manual intervention is required. Examples of terminal errors would be invalid combinations of settings in the spec, values that are unsupported by the controller, or the responsible controller itself being critically misconfigured.\n\nAny transient errors that occur during the reconciliation of OpenStackClusters can be added as events to the OpenStackCluster object and/or logged in the controller's output.\n\nDeprecated: This field is deprecated and will be removed in a future API version. Use status.conditions to report failures.",
Type: []string{"string"},
Format: "",
},
},
"failureMessage": {
SchemaProps: spec.SchemaProps{
- Description: "FailureMessage will be set in the event that there is a terminal problem reconciling the OpenStackCluster and will contain a more verbose string suitable for logging and human consumption.\n\nThis field should not be set for transitive errors that a controller faces that are expected to be fixed automatically over time (like service outages), but instead indicate that something is fundamentally wrong with the OpenStackCluster's spec or the configuration of the controller, and that manual intervention is required. Examples of terminal errors would be invalid combinations of settings in the spec, values that are unsupported by the controller, or the responsible controller itself being critically misconfigured.\n\nAny transient errors that occur during the reconciliation of OpenStackClusters can be added as events to the OpenStackCluster object and/or logged in the controller's output.",
+ Description: "FailureMessage will be set in the event that there is a terminal problem reconciling the OpenStackCluster and will contain a more verbose string suitable for logging and human consumption.\n\nThis field should not be set for transitive errors that a controller faces that are expected to be fixed automatically over time (like service outages), but instead indicate that something is fundamentally wrong with the OpenStackCluster's spec or the configuration of the controller, and that manual intervention is required. Examples of terminal errors would be invalid combinations of settings in the spec, values that are unsupported by the controller, or the responsible controller itself being critically misconfigured.\n\nAny transient errors that occur during the reconciliation of OpenStackClusters can be added as events to the OpenStackCluster object and/or logged in the controller's output.\n\nDeprecated: This field is deprecated and will be removed in a future API version. Use status.conditions to report failures.",
Type: []string{"string"},
Format: "",
},
},
+ "conditions": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Conditions defines current service state of the OpenStackCluster. This field surfaces into Cluster's status.conditions[InfrastructureReady] condition. The Ready condition must surface issues during the entire lifecycle of the OpenStackCluster (both during initial provisioning and after the initial provisioning is completed).",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: map[string]interface{}{},
+ Ref: ref("sigs.k8s.io/cluster-api/api/core/v1beta1.Condition"),
+ },
+ },
+ },
+ },
+ },
},
Required: []string{"ready"},
},
},
Dependencies: []string{
- "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.BastionStatus", "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.LoadBalancer", "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.NetworkStatus", "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.NetworkStatusWithSubnets", "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.Router", "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.SecurityGroupStatus", "sigs.k8s.io/cluster-api/api/core/v1beta1.FailureDomainSpec"},
+ "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.BastionStatus", "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.ClusterInitialization", "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.LoadBalancer", "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.NetworkStatus", "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.NetworkStatusWithSubnets", "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.Router", "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.SecurityGroupStatus", "sigs.k8s.io/cluster-api/api/core/v1beta1.Condition", "sigs.k8s.io/cluster-api/api/core/v1beta1.FailureDomainSpec"},
}
}
@@ -19618,12 +19680,18 @@ func schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_OpenStackMachin
Properties: map[string]spec.Schema{
"ready": {
SchemaProps: spec.SchemaProps{
- Description: "Ready is true when the provider resource is ready.",
+ Description: "Ready is true when the provider resource is ready.\n\nDeprecated: This field is deprecated and will be removed in a future API version. Use status.conditions to determine the ready state of the machine.",
Default: false,
Type: []string{"boolean"},
Format: "",
},
},
+ "initialization": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Initialization contains information about the initialization status of the machine.",
+ Ref: ref("sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.MachineInitialization"),
+ },
+ },
"instanceID": {
SchemaProps: spec.SchemaProps{
Description: "InstanceID is the OpenStack instance ID for this machine.",
@@ -19666,20 +19734,22 @@ func schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_OpenStackMachin
},
"failureReason": {
SchemaProps: spec.SchemaProps{
- Type: []string{"string"},
- Format: "",
+ Description: "FailureReason explains the reson behind a failure.\n\nDeprecated: This field is deprecated and will be removed in a future API version. Use status.conditions to report failures.",
+ Type: []string{"string"},
+ Format: "",
},
},
"failureMessage": {
SchemaProps: spec.SchemaProps{
- Description: "FailureMessage will be set in the event that there is a terminal problem reconciling the Machine and will contain a more verbose string suitable for logging and human consumption.\n\nThis field should not be set for transitive errors that a controller faces that are expected to be fixed automatically over time (like service outages), but instead indicate that something is fundamentally wrong with the Machine's spec or the configuration of the controller, and that manual intervention is required. Examples of terminal errors would be invalid combinations of settings in the spec, values that are unsupported by the controller, or the responsible controller itself being critically misconfigured.\n\nAny transient errors that occur during the reconciliation of Machines can be added as events to the Machine object and/or logged in the controller's output.",
+ Description: "FailureMessage will be set in the event that there is a terminal problem reconciling the Machine and will contain a more verbose string suitable for logging and human consumption.\n\nThis field should not be set for transitive errors that a controller faces that are expected to be fixed automatically over time (like service outages), but instead indicate that something is fundamentally wrong with the Machine's spec or the configuration of the controller, and that manual intervention is required. Examples of terminal errors would be invalid combinations of settings in the spec, values that are unsupported by the controller, or the responsible controller itself being critically misconfigured.\n\nAny transient errors that occur during the reconciliation of Machines can be added as events to the Machine object and/or logged in the controller's output.\n\nDeprecated: This field is deprecated and will be removed in a future API version. Use status.conditions to report failures.",
Type: []string{"string"},
Format: "",
},
},
"conditions": {
SchemaProps: spec.SchemaProps{
- Type: []string{"array"},
+ Description: "Conditions defines current service state of the OpenStackMachine. This field surfaces into Machine's status.conditions[InfrastructureReady] condition. The Ready condition must surface issues during the entire lifecycle of the OpenStackMachine (both during initial provisioning and after the initial provisioning is completed).",
+ Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
@@ -19694,7 +19764,7 @@ func schema_sigsk8sio_cluster_api_provider_openstack_api_v1beta1_OpenStackMachin
},
},
Dependencies: []string{
- "k8s.io/api/core/v1.NodeAddress", "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.MachineResources", "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.ResolvedMachineSpec", "sigs.k8s.io/cluster-api/api/core/v1beta1.Condition"},
+ "k8s.io/api/core/v1.NodeAddress", "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.MachineInitialization", "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.MachineResources", "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.ResolvedMachineSpec", "sigs.k8s.io/cluster-api/api/core/v1beta1.Condition"},
}
}
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclusters.yaml
index 4cf9b3dae..c20a48a00 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclusters.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclusters.yaml
@@ -2461,6 +2461,62 @@ spec:
- id
- name
type: object
+ conditions:
+ description: |-
+ Conditions defines current service state of the OpenStackCluster.
+ This field surfaces into Cluster's status.conditions[InfrastructureReady] condition.
+ The Ready condition must surface issues during the entire lifecycle of the OpenStackCluster
+ (both during initial provisioning and after the initial provisioning is completed).
+ items:
+ description: Condition defines an observation of a Cluster API resource
+ operational state.
+ properties:
+ lastTransitionTime:
+ description: |-
+ lastTransitionTime is the last time the condition transitioned from one status to another.
+ This should be when the underlying condition changed. If that is not known, then using the time when
+ the API field changed is acceptable.
+ format: date-time
+ type: string
+ message:
+ description: |-
+ message is a human readable message indicating details about the transition.
+ This field may be empty.
+ maxLength: 10240
+ minLength: 1
+ type: string
+ reason:
+ description: |-
+ reason is the reason for the condition's last transition in CamelCase.
+ The specific API may choose whether or not this field is considered a guaranteed API.
+ This field may be empty.
+ maxLength: 256
+ minLength: 1
+ type: string
+ severity:
+ description: |-
+ severity provides an explicit classification of Reason code, so the users or machines can immediately
+ understand the current situation and act accordingly.
+ The Severity field MUST be set only when Status=False.
+ maxLength: 32
+ type: string
+ status:
+ description: status of the condition, one of True, False, Unknown.
+ type: string
+ type:
+ description: |-
+ type of condition in CamelCase or in foo.example.com/CamelCase.
+ Many .condition.type values are consistent across resources like Available, but because arbitrary conditions
+ can be useful (see .node.status.conditions), the ability to deconflict is important.
+ maxLength: 256
+ minLength: 1
+ type: string
+ required:
+ - lastTransitionTime
+ - status
+ - type
+ type: object
+ type: array
controlPlaneSecurityGroup:
description: |-
ControlPlaneSecurityGroup contains the information about the
@@ -2530,6 +2586,9 @@ spec:
Any transient errors that occur during the reconciliation of
OpenStackClusters can be added as events to the OpenStackCluster object
and/or logged in the controller's output.
+
+ Deprecated: This field is deprecated and will be removed in a future API version.
+ Use status.conditions to report failures.
type: string
failureReason:
description: |-
@@ -2549,7 +2608,20 @@ spec:
Any transient errors that occur during the reconciliation of
OpenStackClusters can be added as events to the OpenStackCluster object
and/or logged in the controller's output.
+
+ Deprecated: This field is deprecated and will be removed in a future API version.
+ Use status.conditions to report failures.
type: string
+ initialization:
+ description: Initialization contains information about the initialization
+ status of the cluster.
+ properties:
+ provisioned:
+ description: |-
+ Provisioned is set to true when the initial provisioning of the cluster infrastructure is completed.
+ The value of this field is never updated after provisioning is completed.
+ type: boolean
+ type: object
network:
description: Network contains information about the created OpenStack
Network.
@@ -2592,7 +2664,11 @@ spec:
type: object
ready:
default: false
- description: Ready is true when the cluster infrastructure is ready.
+ description: |-
+ Ready is true when the cluster infrastructure is ready.
+
+ Deprecated: This field is deprecated and will be removed in a future API version.
+ Use status.conditions to determine the ready state of the cluster.
type: boolean
router:
description: Router describes the default cluster router
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackmachines.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackmachines.yaml
index 0dc88ef8c..e752a7d2f 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackmachines.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackmachines.yaml
@@ -967,8 +967,11 @@ spec:
type: object
type: array
conditions:
- description: Conditions provide observations of the operational state
- of a Cluster API resource.
+ description: |-
+ Conditions defines current service state of the OpenStackMachine.
+ This field surfaces into Machine's status.conditions[InfrastructureReady] condition.
+ The Ready condition must surface issues during the entire lifecycle of the OpenStackMachine
+ (both during initial provisioning and after the initial provisioning is completed).
items:
description: Condition defines an observation of a Cluster API resource
operational state.
@@ -1037,11 +1040,27 @@ spec:
Any transient errors that occur during the reconciliation of Machines
can be added as events to the Machine object and/or logged in the
controller's output.
+
+ Deprecated: This field is deprecated and will be removed in a future API version.
+ Use status.conditions to report failures.
type: string
failureReason:
- description: DeprecatedCAPIMachineStatusError defines errors states
- for Machine objects.
+ description: |-
+ FailureReason explains the reson behind a failure.
+
+ Deprecated: This field is deprecated and will be removed in a future API version.
+ Use status.conditions to report failures.
type: string
+ initialization:
+ description: Initialization contains information about the initialization
+ status of the machine.
+ properties:
+ provisioned:
+ description: |-
+ Provisioned is set to true when the initial provisioning of the machine infrastructure is completed.
+ The value of this field is never updated after provisioning is completed.
+ type: boolean
+ type: object
instanceID:
description: InstanceID is the OpenStack instance ID for this machine.
type: string
@@ -1052,7 +1071,11 @@ spec:
Instead, it's set by the OpenStackServer controller.
type: string
ready:
- description: Ready is true when the provider resource is ready.
+ description: |-
+ Ready is true when the provider resource is ready.
+
+ Deprecated: This field is deprecated and will be removed in a future API version.
+ Use status.conditions to determine the ready state of the machine.
type: boolean
resolved:
description: |-
diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml
index 720fff6c0..ff5280841 100644
--- a/config/default/kustomization.yaml
+++ b/config/default/kustomization.yaml
@@ -21,134 +21,138 @@ patches:
- path: manager_webhook_patch.yaml
replacements:
- - source: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs
+- source:
+ # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs
+ kind: Certificate
+ group: cert-manager.io
+ version: v1
+ name: serving-cert # this name should match the one in certificate.yaml
+ fieldPath: .metadata.namespace # namespace of the certificate CR
+ targets:
+ - select:
+ kind: ValidatingWebhookConfiguration
+ fieldPaths:
+ - .metadata.annotations.[cert-manager.io/inject-ca-from]
+ options:
+ delimiter: '/'
+ index: 0
+ create: true
+ - select:
+ kind: MutatingWebhookConfiguration
+ fieldPaths:
+ - .metadata.annotations.[cert-manager.io/inject-ca-from]
+ options:
+ delimiter: '/'
+ index: 0
+ create: true
+ - select:
+ kind: CustomResourceDefinition
+ fieldPaths:
+ - .metadata.annotations.[cert-manager.io/inject-ca-from]
+ options:
+ delimiter: '/'
+ index: 0
+ create: true
+- source:
+ kind: Certificate
+ group: cert-manager.io
+ version: v1
+ name: serving-cert # this name should match the one in certificate.yaml
+ fieldPath: .metadata.name
+ targets:
+ - select:
+ kind: ValidatingWebhookConfiguration
+ fieldPaths:
+ - .metadata.annotations.[cert-manager.io/inject-ca-from]
+ options:
+ delimiter: '/'
+ index: 1
+ create: true
+ - select:
+ kind: MutatingWebhookConfiguration
+ fieldPaths:
+ - .metadata.annotations.[cert-manager.io/inject-ca-from]
+ options:
+ delimiter: '/'
+ index: 1
+ create: true
+ - select:
+ kind: CustomResourceDefinition
+ fieldPaths:
+ - .metadata.annotations.[cert-manager.io/inject-ca-from]
+ options:
+ delimiter: '/'
+ index: 1
+ create: true
+- source:
+ # Add cert-manager annotation to the webhook Service
+ kind: Service
+ version: v1
+ name: webhook-service
+ fieldPath: .metadata.name # namespace of the service
+ targets:
+ - select:
kind: Certificate
group: cert-manager.io
version: v1
- name: serving-cert # this name should match the one in certificate.yaml
- fieldPath: .metadata.namespace # namespace of the certificate CR
- targets:
- - select:
- kind: ValidatingWebhookConfiguration
- fieldPaths:
- - .metadata.annotations.[cert-manager.io/inject-ca-from]
- options:
- delimiter: '/'
- index: 0
- create: true
- - select:
- kind: MutatingWebhookConfiguration
- fieldPaths:
- - .metadata.annotations.[cert-manager.io/inject-ca-from]
- options:
- delimiter: '/'
- index: 0
- create: true
- - select:
- kind: CustomResourceDefinition
- fieldPaths:
- - .metadata.annotations.[cert-manager.io/inject-ca-from]
- options:
- delimiter: '/'
- index: 0
- create: true
- - source:
+ name: serving-cert
+ fieldPaths:
+ - .spec.dnsNames.0
+ - .spec.dnsNames.1
+ options:
+ delimiter: '.'
+ index: 0
+ create: false
+- source:
+ kind: Service
+ version: v1
+ name: webhook-service
+ fieldPath: .metadata.namespace # namespace of the service
+ targets:
+ - select:
kind: Certificate
group: cert-manager.io
version: v1
- name: serving-cert # this name should match the one in certificate.yaml
- fieldPath: .metadata.name
- targets:
- - select:
- kind: ValidatingWebhookConfiguration
- fieldPaths:
- - .metadata.annotations.[cert-manager.io/inject-ca-from]
- options:
- delimiter: '/'
- index: 1
- create: true
- - select:
- kind: MutatingWebhookConfiguration
- fieldPaths:
- - .metadata.annotations.[cert-manager.io/inject-ca-from]
- options:
- delimiter: '/'
- index: 1
- create: true
- - select:
- kind: CustomResourceDefinition
- fieldPaths:
- - .metadata.annotations.[cert-manager.io/inject-ca-from]
- options:
- delimiter: '/'
- index: 1
- create: true
- - source: # Add cert-manager annotation to the webhook Service
- kind: Service
- version: v1
- name: webhook-service
- fieldPath: .metadata.name # namespace of the service
- targets:
- - select:
- kind: Certificate
- group: cert-manager.io
- version: v1
- name: serving-cert
- fieldPaths:
- - .spec.dnsNames.0
- - .spec.dnsNames.1
- options:
- delimiter: '.'
- index: 0
- create: false
- - source:
- kind: Service
- version: v1
- name: webhook-service
- fieldPath: .metadata.namespace # namespace of the service
- targets:
- - select:
- kind: Certificate
- group: cert-manager.io
- version: v1
- name: serving-cert
- fieldPaths:
- - .spec.dnsNames.0
- - .spec.dnsNames.1
- options:
- delimiter: '.'
- index: 1
- create: false
- - source: # Prefix the certificate secret name with the name of service
- kind: Service
- version: v1
- name: webhook-service
- fieldPath: .metadata.name # namespace of the service
- targets:
- - select:
- kind: Certificate
- group: cert-manager.io
- version: v1
- fieldPaths:
- - .spec.secretName
- options:
- delimiter: '-'
- index: 0
- create: false
- - source: # Certificate secret name
+ name: serving-cert
+ fieldPaths:
+ - .spec.dnsNames.0
+ - .spec.dnsNames.1
+ options:
+ delimiter: '.'
+ index: 1
+ create: false
+- source:
+ # Prefix the certificate secret name with the name of service
+ kind: Service
+ version: v1
+ name: webhook-service
+ fieldPath: .metadata.name # namespace of the service
+ targets:
+ - select:
kind: Certificate
group: cert-manager.io
version: v1
- name: serving-cert
- fieldPath: .spec.secretName
- targets:
- - select:
- kind: Deployment
- group: apps
- version: v1
- name: controller-manager
- fieldPaths:
- - .spec.template.spec.volumes.[name=cert].secret.secretName
+ fieldPaths:
+ - .spec.secretName
+ options:
+ delimiter: '-'
+ index: 0
+ create: false
+- source:
+ # Certificate secret name
+ kind: Certificate
+ group: cert-manager.io
+ version: v1
+ name: serving-cert
+ fieldPath: .spec.secretName
+ targets:
+ - select:
+ kind: Deployment
+ group: apps
+ version: v1
+ name: controller-manager
+ fieldPaths:
+ - .spec.template.spec.volumes.[name=cert].secret.secretName
configurations:
- - kustomizeconfig.yaml
+- kustomizeconfig.yaml
diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml
index 4aab6e0d3..4177630c3 100644
--- a/config/manager/manager.yaml
+++ b/config/manager/manager.yaml
@@ -17,37 +17,38 @@ spec:
control-plane: capo-controller-manager
spec:
containers:
- - command:
- - /manager
- args:
- - "--leader-elect"
- - "--v=2"
- - "--diagnostics-address=127.0.0.1:8080"
- - "--insecure-diagnostics=true"
- image: controller:latest
- imagePullPolicy: Always
- name: manager
- ports:
- - containerPort: 9440
- name: healthz
- protocol: TCP
- readinessProbe:
- httpGet:
- path: /readyz
- port: healthz
- livenessProbe:
- httpGet:
- path: /healthz
- port: healthz
- securityContext:
- allowPrivilegeEscalation: false
- capabilities:
- drop:
- - ALL
- privileged: false
- runAsUser: 65532
- runAsGroup: 65532
- terminationMessagePolicy: FallbackToLogsOnError
+ - command:
+ - /manager
+ args:
+ - "--leader-elect"
+ - "--v=2"
+ - "--diagnostics-address=127.0.0.1:8080"
+ - "--insecure-diagnostics=true"
+ - "--feature-gates=PriorityQueue=${EXP_CAPO_PRIORITY_QUEUE:=false}"
+ image: controller:latest
+ imagePullPolicy: Always
+ name: manager
+ ports:
+ - containerPort: 9440
+ name: healthz
+ protocol: TCP
+ readinessProbe:
+ httpGet:
+ path: /readyz
+ port: healthz
+ livenessProbe:
+ httpGet:
+ path: /healthz
+ port: healthz
+ securityContext:
+ allowPrivilegeEscalation: false
+ capabilities:
+ drop:
+ - ALL
+ privileged: false
+ runAsUser: 65532
+ runAsGroup: 65532
+ terminationMessagePolicy: FallbackToLogsOnError
terminationGracePeriodSeconds: 10
securityContext:
runAsNonRoot: true
@@ -55,7 +56,7 @@ spec:
type: RuntimeDefault
serviceAccountName: manager
tolerations:
- - effect: NoSchedule
- key: node-role.kubernetes.io/master
- - effect: NoSchedule
- key: node-role.kubernetes.io/control-plane
+ - effect: NoSchedule
+ key: node-role.kubernetes.io/master
+ - effect: NoSchedule
+ key: node-role.kubernetes.io/control-plane
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
index dc44ee6b1..70662a53b 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -24,6 +24,17 @@ rules:
- get
- list
- watch
+- apiGroups:
+ - apiextensions.k8s.io
+ resources:
+ - customresourcedefinitions
+ - customresourcedefinitions/status
+ verbs:
+ - get
+ - list
+ - patch
+ - update
+ - watch
- apiGroups:
- authentication.k8s.io
resources:
@@ -51,10 +62,28 @@ rules:
- infrastructure.cluster.x-k8s.io
resources:
- openstackclusteridentities
+ - openstackclustertemplates
+ - openstackmachinetemplates
verbs:
- get
- list
+ - patch
+ - update
- watch
+- apiGroups:
+ - infrastructure.cluster.x-k8s.io
+ resources:
+ - openstackclusteridentities/status
+ - openstackclusters/status
+ - openstackclustertemplates/status
+ - openstackfloatingippools/status
+ - openstackmachines/status
+ - openstackmachinetemplates/status
+ - openstackservers/status
+ verbs:
+ - get
+ - patch
+ - update
- apiGroups:
- infrastructure.cluster.x-k8s.io
resources:
@@ -70,17 +99,6 @@ rules:
- patch
- update
- watch
-- apiGroups:
- - infrastructure.cluster.x-k8s.io
- resources:
- - openstackclusters/status
- - openstackfloatingippools/status
- - openstackmachines/status
- - openstackservers/status
- verbs:
- - get
- - patch
- - update
- apiGroups:
- ipam.cluster.x-k8s.io
resources:
diff --git a/config/webhook/kustomization.yaml b/config/webhook/kustomization.yaml
index 9cf26134e..36d4cc6e4 100644
--- a/config/webhook/kustomization.yaml
+++ b/config/webhook/kustomization.yaml
@@ -1,6 +1,3 @@
resources:
- manifests.yaml
- service.yaml
-
-configurations:
-- kustomizeconfig.yaml
diff --git a/config/webhook/kustomizeconfig.yaml b/config/webhook/kustomizeconfig.yaml
deleted file mode 100644
index 25e21e3c9..000000000
--- a/config/webhook/kustomizeconfig.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
-# the following config is for teaching kustomize where to look at when substituting vars.
-# It requires kustomize v2.1.0 or newer to work properly.
-nameReference:
-- kind: Service
- version: v1
- fieldSpecs:
- - kind: MutatingWebhookConfiguration
- group: admissionregistration.k8s.io
- path: webhooks/clientConfig/service/name
- - kind: ValidatingWebhookConfiguration
- group: admissionregistration.k8s.io
- path: webhooks/clientConfig/service/name
-
-namespace:
-- kind: MutatingWebhookConfiguration
- group: admissionregistration.k8s.io
- path: webhooks/clientConfig/service/namespace
- create: true
-- kind: ValidatingWebhookConfiguration
- group: admissionregistration.k8s.io
- path: webhooks/clientConfig/service/namespace
- create: true
-
-varReference:
-- path: metadata/annotations
diff --git a/config/webhook/service.yaml b/config/webhook/service.yaml
index 711977f54..db070dff2 100644
--- a/config/webhook/service.yaml
+++ b/config/webhook/service.yaml
@@ -5,5 +5,5 @@ metadata:
namespace: system
spec:
ports:
- - port: 443
- targetPort: webhook-server
+ - port: 443
+ targetPort: webhook-server
diff --git a/controllers/openstackcluster_controller.go b/controllers/openstackcluster_controller.go
index 3989fc155..12a7843e0 100644
--- a/controllers/openstackcluster_controller.go
+++ b/controllers/openstackcluster_controller.go
@@ -37,6 +37,7 @@ import (
"sigs.k8s.io/cluster-api/util"
"sigs.k8s.io/cluster-api/util/annotations"
"sigs.k8s.io/cluster-api/util/collections"
+ v1beta1conditions "sigs.k8s.io/cluster-api/util/deprecated/v1beta1/conditions"
"sigs.k8s.io/cluster-api/util/patch"
"sigs.k8s.io/cluster-api/util/predicates"
ctrl "sigs.k8s.io/controller-runtime"
@@ -356,6 +357,24 @@ func (r *OpenStackClusterReconciler) reconcileNormal(ctx context.Context, scope
openStackCluster.Status.Ready = true
openStackCluster.Status.FailureMessage = nil
openStackCluster.Status.FailureReason = nil
+
+ // Set initialization.provisioned to true when initial infrastructure provisioning is complete.
+ // This field should only be set once and never changed afterward, as per CAPI v1beta2 contract.
+ // We set it here after all core infrastructure (network, router, security groups, control plane endpoint)
+ // has been successfully provisioned.
+ if openStackCluster.Status.Initialization == nil {
+ openStackCluster.Status.Initialization = &infrav1.ClusterInitialization{}
+ }
+ if !openStackCluster.Status.Initialization.Provisioned {
+ openStackCluster.Status.Initialization.Provisioned = true
+ scope.Logger().Info("Initial cluster infrastructure provisioning completed")
+ }
+
+ // Set the Ready condition to True when infrastructure is ready.
+ // This condition surfaces into Cluster's status.conditions[InfrastructureReady].
+ // It reflects the current operational state of the cluster infrastructure.
+ v1beta1conditions.MarkTrue(openStackCluster, clusterv1beta1.ReadyCondition)
+
scope.Logger().Info("Reconciled Cluster created successfully")
result, err := r.reconcileBastion(ctx, scope, cluster, openStackCluster)
@@ -623,60 +642,68 @@ func getBastionSecurityGroupID(openStackCluster *infrav1.OpenStackCluster) *stri
func resolveLoadBalancerNetwork(openStackCluster *infrav1.OpenStackCluster, networkingService *networking.Service) error {
lbSpec := openStackCluster.Spec.APIServerLoadBalancer
- if lbSpec.IsEnabled() {
- lbStatus := openStackCluster.Status.APIServerLoadBalancer
- if lbStatus == nil {
- lbStatus = &infrav1.LoadBalancer{}
- openStackCluster.Status.APIServerLoadBalancer = lbStatus
+
+ // if lb is not enabled, return early
+ if !lbSpec.IsEnabled() {
+ return nil
+ }
+
+ lbStatus := openStackCluster.Status.APIServerLoadBalancer
+ if lbStatus == nil {
+ lbStatus = &infrav1.LoadBalancer{}
+ openStackCluster.Status.APIServerLoadBalancer = lbStatus
+ }
+
+ lbNetStatus := lbStatus.LoadBalancerNetwork
+ if lbNetStatus == nil {
+ lbNetStatus = &infrav1.NetworkStatusWithSubnets{
+ NetworkStatus: infrav1.NetworkStatus{},
}
+ }
- lbNetStatus := lbStatus.LoadBalancerNetwork
- if lbNetStatus == nil {
- lbNetStatus = &infrav1.NetworkStatusWithSubnets{
- NetworkStatus: infrav1.NetworkStatus{},
+ if lbSpec.Network != nil {
+ lbNet, err := networkingService.GetNetworkByParam(lbSpec.Network)
+ if err != nil {
+ if errors.Is(err, capoerrors.ErrFilterMatch) {
+ handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to find loadbalancer network: %w", err), true)
}
+ return fmt.Errorf("failed to find network: %w", err)
}
- if lbSpec.Network != nil {
- lbNet, err := networkingService.GetNetworkByParam(lbSpec.Network)
- if err != nil {
- if errors.Is(err, capoerrors.ErrFilterMatch) {
- handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to find loadbalancer network: %w", err), true)
+ lbNetStatus.Name = lbNet.Name
+ lbNetStatus.ID = lbNet.ID
+ lbNetStatus.Tags = lbNet.Tags
+
+ // Filter out only relevant subnets specified by the spec
+ lbNetStatus.Subnets = []infrav1.Subnet{}
+ for _, s := range lbSpec.Subnets {
+ matchFound := false
+ for _, subnetID := range lbNet.Subnets {
+ subnet, err := networkingService.GetSubnetByParam(&s)
+ if s.ID != nil && subnetID == *s.ID && err == nil {
+ matchFound = true
+ lbNetStatus.Subnets = append(
+ lbNetStatus.Subnets, infrav1.Subnet{
+ ID: subnet.ID,
+ Name: subnet.Name,
+ CIDR: subnet.CIDR,
+ Tags: subnet.Tags,
+ })
}
- return fmt.Errorf("failed to find network: %w", err)
}
-
- lbNetStatus.Name = lbNet.Name
- lbNetStatus.ID = lbNet.ID
- lbNetStatus.Tags = lbNet.Tags
-
- // Filter out only relevant subnets specified by the spec
- lbNetStatus.Subnets = []infrav1.Subnet{}
- for _, s := range lbSpec.Subnets {
- matchFound := false
- for _, subnetID := range lbNet.Subnets {
- subnet, err := networkingService.GetSubnetByParam(&s)
- if s.ID != nil && subnetID == *s.ID && err == nil {
- matchFound = true
- lbNetStatus.Subnets = append(
- lbNetStatus.Subnets, infrav1.Subnet{
- ID: subnet.ID,
- Name: subnet.Name,
- CIDR: subnet.CIDR,
- Tags: subnet.Tags,
- })
- }
- }
- if !matchFound {
- handleUpdateOSCError(openStackCluster, fmt.Errorf("no subnet match was found in the specified network (specified subnet: %v, available subnets: %v)", s, lbNet.Subnets), false)
- return fmt.Errorf("no subnet match was found in the specified network (specified subnet: %v, available subnets: %v)", s, lbNet.Subnets)
- }
+ if !matchFound {
+ handleUpdateOSCError(openStackCluster, fmt.Errorf("no subnet match was found in the specified network (specified subnet: %v, available subnets: %v)", s, lbNet.Subnets), false)
+ return fmt.Errorf("no subnet match was found in the specified network (specified subnet: %v, available subnets: %v)", s, lbNet.Subnets)
}
-
- openStackCluster.Status.APIServerLoadBalancer.LoadBalancerNetwork = lbNetStatus
}
+ } else {
+ lbNetStatus.ID = openStackCluster.Status.Network.ID
+ lbNetStatus.Name = openStackCluster.Status.Network.Name
+ lbNetStatus.Subnets = openStackCluster.Status.Network.Subnets
}
+ openStackCluster.Status.APIServerLoadBalancer.LoadBalancerNetwork = lbNetStatus
+
return nil
}
@@ -716,11 +743,20 @@ func reconcileNetworkComponents(scope *scope.WithLogger, cluster *clusterv1.Clus
err = networkingService.ReconcileSecurityGroups(openStackCluster, clusterResourceName)
if err != nil {
+ v1beta1conditions.MarkFalse(openStackCluster, infrav1.SecurityGroupsReadyCondition, infrav1.SecurityGroupReconcileFailedReason, clusterv1beta1.ConditionSeverityError, "Failed to reconcile security groups: %v", err)
handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to reconcile security groups: %w", err), false)
return fmt.Errorf("failed to reconcile security groups: %w", err)
}
+ v1beta1conditions.MarkTrue(openStackCluster, infrav1.SecurityGroupsReadyCondition)
- return reconcileControlPlaneEndpoint(scope, networkingService, openStackCluster, clusterResourceName)
+ err = reconcileControlPlaneEndpoint(scope, networkingService, openStackCluster, clusterResourceName)
+ if err != nil {
+ v1beta1conditions.MarkFalse(openStackCluster, infrav1.APIEndpointReadyCondition, infrav1.APIEndpointConfigFailedReason, clusterv1beta1.ConditionSeverityError, "Failed to reconcile control plane endpoint: %v", err)
+ return err
+ }
+ v1beta1conditions.MarkTrue(openStackCluster, infrav1.APIEndpointReadyCondition)
+
+ return nil
}
// reconcilePreExistingNetworkComponents reconciles the cluster network status when the cluster is
@@ -736,6 +772,7 @@ func reconcilePreExistingNetworkComponents(scope *scope.WithLogger, networkingSe
if openStackCluster.Spec.Network != nil {
network, err := networkingService.GetNetworkByParam(openStackCluster.Spec.Network)
if err != nil {
+ v1beta1conditions.MarkFalse(openStackCluster, infrav1.NetworkReadyCondition, infrav1.OpenStackErrorReason, clusterv1beta1.ConditionSeverityError, "Failed to find network: %v", err)
handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to find network: %w", err), false)
return fmt.Errorf("error fetching cluster network: %w", err)
}
@@ -744,6 +781,7 @@ func reconcilePreExistingNetworkComponents(scope *scope.WithLogger, networkingSe
subnets, err := getClusterSubnets(networkingService, openStackCluster)
if err != nil {
+ v1beta1conditions.MarkFalse(openStackCluster, infrav1.NetworkReadyCondition, infrav1.OpenStackErrorReason, clusterv1beta1.ConditionSeverityError, "Failed to get cluster subnets: %v", err)
return err
}
@@ -759,6 +797,7 @@ func reconcilePreExistingNetworkComponents(scope *scope.WithLogger, networkingSe
}
}
if err := utils.ValidateSubnets(capoSubnets); err != nil {
+ v1beta1conditions.MarkFalse(openStackCluster, infrav1.NetworkReadyCondition, infrav1.OpenStackErrorReason, clusterv1beta1.ConditionSeverityError, "Failed to validate subnets: %v", err)
return err
}
openStackCluster.Status.Network.Subnets = capoSubnets
@@ -769,14 +808,18 @@ func reconcilePreExistingNetworkComponents(scope *scope.WithLogger, networkingSe
if openStackCluster.Status.Network.ID == "" && len(subnets) > 0 {
network, err := networkingService.GetNetworkByID(subnets[0].NetworkID)
if err != nil {
+ v1beta1conditions.MarkFalse(openStackCluster, infrav1.NetworkReadyCondition, infrav1.OpenStackErrorReason, clusterv1beta1.ConditionSeverityError, "Failed to get network by ID: %v", err)
return err
}
setClusterNetwork(openStackCluster, network)
}
+ v1beta1conditions.MarkTrue(openStackCluster, infrav1.NetworkReadyCondition)
+
if openStackCluster.Spec.Router != nil {
router, err := networkingService.GetRouterByParam(openStackCluster.Spec.Router)
if err != nil {
+ v1beta1conditions.MarkFalse(openStackCluster, infrav1.RouterReadyCondition, infrav1.OpenStackErrorReason, clusterv1beta1.ConditionSeverityError, "Failed to find router: %v", err)
handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to find router: %w", err), false)
return fmt.Errorf("error fetching cluster router: %w", err)
}
@@ -794,6 +837,7 @@ func reconcilePreExistingNetworkComponents(scope *scope.WithLogger, networkingSe
Tags: router.Tags,
IPs: routerIPs,
}
+ v1beta1conditions.MarkTrue(openStackCluster, infrav1.RouterReadyCondition)
}
return nil
@@ -804,19 +848,25 @@ func reconcilePreExistingNetworkComponents(scope *scope.WithLogger, networkingSe
func reconcileProvisionedNetworkComponents(networkingService *networking.Service, openStackCluster *infrav1.OpenStackCluster, clusterResourceName string) error {
err := networkingService.ReconcileNetwork(openStackCluster, clusterResourceName)
if err != nil {
+ v1beta1conditions.MarkFalse(openStackCluster, infrav1.NetworkReadyCondition, infrav1.NetworkReconcileFailedReason, clusterv1beta1.ConditionSeverityError, "Failed to reconcile network: %v", err)
handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to reconcile network: %w", err), false)
return fmt.Errorf("failed to reconcile network: %w", err)
}
err = networkingService.ReconcileSubnet(openStackCluster, clusterResourceName)
if err != nil {
+ v1beta1conditions.MarkFalse(openStackCluster, infrav1.NetworkReadyCondition, infrav1.SubnetReconcileFailedReason, clusterv1beta1.ConditionSeverityError, "Failed to reconcile subnets: %v", err)
handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to reconcile subnets: %w", err), false)
return fmt.Errorf("failed to reconcile subnets: %w", err)
}
+ v1beta1conditions.MarkTrue(openStackCluster, infrav1.NetworkReadyCondition)
+
err = networkingService.ReconcileRouter(openStackCluster, clusterResourceName)
if err != nil {
+ v1beta1conditions.MarkFalse(openStackCluster, infrav1.RouterReadyCondition, infrav1.RouterReconcileFailedReason, clusterv1beta1.ConditionSeverityError, "Failed to reconcile router: %v", err)
handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to reconcile router: %w", err), false)
return fmt.Errorf("failed to reconcile router: %w", err)
}
+ v1beta1conditions.MarkTrue(openStackCluster, infrav1.RouterReadyCondition)
return nil
}
@@ -947,6 +997,12 @@ func handleUpdateOSCError(openstackCluster *infrav1.OpenStackCluster, message er
err := capoerrors.DeprecatedCAPOUpdateClusterError
openstackCluster.Status.FailureReason = &err
openstackCluster.Status.FailureMessage = ptr.To(message.Error())
+ // Set the Ready condition to False for fatal errors
+ v1beta1conditions.MarkFalse(openstackCluster, clusterv1beta1.ReadyCondition, infrav1.OpenStackErrorReason, clusterv1beta1.ConditionSeverityError, "%v", message)
+ } else {
+ // For transient (non-fatal) errors, set Ready condition to False with Warning severity
+ // This indicates a temporary issue that may be resolved on retry
+ v1beta1conditions.MarkFalse(openstackCluster, clusterv1beta1.ReadyCondition, infrav1.OpenStackErrorReason, clusterv1beta1.ConditionSeverityWarning, "%v", message)
}
}
diff --git a/controllers/openstackcluster_controller_test.go b/controllers/openstackcluster_controller_test.go
index 548e7a28c..98f0bd4a8 100644
--- a/controllers/openstackcluster_controller_test.go
+++ b/controllers/openstackcluster_controller_test.go
@@ -22,7 +22,9 @@ import (
"reflect"
"testing"
+ "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/layer3/floatingips"
"github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/layer3/routers"
+ "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/security/groups"
"github.com/gophercloud/gophercloud/v2/openstack/networking/v2/networks"
"github.com/gophercloud/gophercloud/v2/openstack/networking/v2/subnets"
. "github.com/onsi/ginkgo/v2" //nolint:revive
@@ -35,6 +37,7 @@ import (
clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2"
"sigs.k8s.io/cluster-api/test/framework"
"sigs.k8s.io/cluster-api/util/annotations"
+ v1beta1conditions "sigs.k8s.io/cluster-api/util/deprecated/v1beta1/conditions"
"sigs.k8s.io/cluster-api/util/patch"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
@@ -446,6 +449,11 @@ var _ = Describe("OpenStackCluster controller", func() {
err = reconcileNetworkComponents(scope, capiCluster, testCluster)
Expect(err).To(BeNil())
+
+ // Verify conditions are set correctly
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.NetworkReadyCondition)).To(BeTrue())
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.SecurityGroupsReadyCondition)).To(BeTrue())
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.APIEndpointReadyCondition)).To(BeTrue())
})
It("should allow two subnets for the cluster network", func() {
@@ -528,6 +536,11 @@ var _ = Describe("OpenStackCluster controller", func() {
err = reconcileNetworkComponents(scope, capiCluster, testCluster)
Expect(err).To(BeNil())
Expect(len(testCluster.Status.Network.Subnets)).To(Equal(2))
+
+ // Verify conditions are set correctly
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.NetworkReadyCondition)).To(BeTrue())
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.SecurityGroupsReadyCondition)).To(BeTrue())
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.APIEndpointReadyCondition)).To(BeTrue())
})
It("should allow fetch network by subnet", func() {
@@ -574,6 +587,11 @@ var _ = Describe("OpenStackCluster controller", func() {
err = reconcileNetworkComponents(scope, capiCluster, testCluster)
Expect(err).To(BeNil())
Expect(testCluster.Status.Network.ID).To(Equal(clusterNetworkID))
+
+ // Verify conditions are set correctly
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.NetworkReadyCondition)).To(BeTrue())
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.SecurityGroupsReadyCondition)).To(BeTrue())
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.APIEndpointReadyCondition)).To(BeTrue())
})
It("reconcile pre-existing network components by id", func() {
@@ -634,6 +652,10 @@ var _ = Describe("OpenStackCluster controller", func() {
Expect(testCluster.Status.Network.ID).To(Equal(clusterNetworkID))
Expect(testCluster.Status.Network.Subnets[0].ID).To(Equal(clusterSubnetID))
Expect(testCluster.Status.Router.ID).To(Equal(clusterRouterID))
+
+ // Verify conditions are set correctly
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.NetworkReadyCondition)).To(BeTrue())
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.RouterReadyCondition)).To(BeTrue())
})
It("reconcile pre-existing network components by name", func() {
@@ -716,6 +738,462 @@ var _ = Describe("OpenStackCluster controller", func() {
Expect(testCluster.Status.Network.ID).To(Equal(clusterNetworkID))
Expect(testCluster.Status.Network.Subnets[0].ID).To(Equal(clusterSubnetID))
Expect(testCluster.Status.Router.ID).To(Equal(clusterRouterID))
+
+ // Verify conditions are set correctly
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.NetworkReadyCondition)).To(BeTrue())
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.RouterReadyCondition)).To(BeTrue())
+ })
+
+ It("should reconcile API endpoint with floating IP and set condition", func() {
+ const externalNetworkID = "a42211a2-4d2c-426f-9413-830e4b4abbbc"
+ const clusterNetworkID = "6c90b532-7ba0-418a-a276-5ae55060b5b0"
+ const clusterSubnetID = "cad5a91a-36de-4388-823b-b0cc82cadfdc"
+ const floatingIP = "203.0.113.10"
+
+ testCluster.SetName("api-endpoint-floating-ip")
+ testCluster.Spec = infrav1.OpenStackClusterSpec{
+ IdentityRef: infrav1.OpenStackIdentityReference{
+ Name: "test-creds",
+ CloudName: "openstack",
+ },
+ ExternalNetwork: &infrav1.NetworkParam{
+ ID: ptr.To(externalNetworkID),
+ },
+ Network: &infrav1.NetworkParam{
+ ID: ptr.To(clusterNetworkID),
+ },
+ // When DisableAPIServerFloatingIP is not set and external network is configured,
+ // a floating IP should be created for the API server
+ }
+ err := k8sClient.Create(ctx, testCluster)
+ Expect(err).To(BeNil())
+ err = k8sClient.Create(ctx, capiCluster)
+ Expect(err).To(BeNil())
+
+ log := GinkgoLogr
+ clientScope, err := mockScopeFactory.NewClientScopeFromObject(ctx, k8sClient, nil, log, testCluster)
+ Expect(err).To(BeNil())
+ scope := scope.NewWithLogger(clientScope, log)
+
+ networkClientRecorder := mockScopeFactory.NetworkClient.EXPECT()
+
+ // Fetch external network
+ networkClientRecorder.GetNetwork(externalNetworkID).Return(&networks.Network{
+ ID: externalNetworkID,
+ Name: "external-network",
+ }, nil)
+
+ // Fetch cluster network
+ networkClientRecorder.GetNetwork(clusterNetworkID).Return(&networks.Network{
+ ID: clusterNetworkID,
+ Name: "cluster-network",
+ }, nil)
+
+ // Fetching cluster subnets
+ networkClientRecorder.ListSubnet(subnets.ListOpts{
+ NetworkID: clusterNetworkID,
+ }).Return([]subnets.Subnet{
+ {
+ ID: clusterSubnetID,
+ Name: "cluster-subnet",
+ CIDR: "192.168.0.0/24",
+ },
+ }, nil)
+
+ // Mock floating IP creation for API server
+ // When no specific IP is requested, it will just create a new floating IP
+ networkClientRecorder.CreateFloatingIP(gomock.Any()).Return(&floatingips.FloatingIP{
+ FloatingIP: floatingIP,
+ ID: "floating-ip-id",
+ }, nil)
+
+ err = reconcileNetworkComponents(scope, capiCluster, testCluster)
+ Expect(err).To(BeNil())
+
+ // Verify API endpoint was set
+ Expect(testCluster.Spec.ControlPlaneEndpoint).ToNot(BeNil())
+ Expect(testCluster.Spec.ControlPlaneEndpoint.Host).To(Equal(floatingIP))
+ Expect(testCluster.Spec.ControlPlaneEndpoint.Port).To(Equal(int32(6443)))
+
+ // Verify conditions are set correctly
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.NetworkReadyCondition)).To(BeTrue())
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.SecurityGroupsReadyCondition)).To(BeTrue())
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.APIEndpointReadyCondition)).To(BeTrue())
+ })
+
+ It("should reconcile API endpoint with fixed IP and set condition", func() {
+ const clusterNetworkID = "6c90b532-7ba0-418a-a276-5ae55060b5b0"
+ const clusterSubnetID = "cad5a91a-36de-4388-823b-b0cc82cadfdc"
+ const fixedIP = "192.168.0.10"
+
+ testCluster.SetName("api-endpoint-fixed-ip")
+ testCluster.Spec = infrav1.OpenStackClusterSpec{
+ IdentityRef: infrav1.OpenStackIdentityReference{
+ Name: "test-creds",
+ CloudName: "openstack",
+ },
+ Network: &infrav1.NetworkParam{
+ ID: ptr.To(clusterNetworkID),
+ },
+ DisableExternalNetwork: ptr.To(true),
+ DisableAPIServerFloatingIP: ptr.To(true),
+ APIServerFixedIP: ptr.To(fixedIP),
+ }
+ err := k8sClient.Create(ctx, testCluster)
+ Expect(err).To(BeNil())
+ err = k8sClient.Create(ctx, capiCluster)
+ Expect(err).To(BeNil())
+
+ log := GinkgoLogr
+ clientScope, err := mockScopeFactory.NewClientScopeFromObject(ctx, k8sClient, nil, log, testCluster)
+ Expect(err).To(BeNil())
+ scope := scope.NewWithLogger(clientScope, log)
+
+ networkClientRecorder := mockScopeFactory.NetworkClient.EXPECT()
+
+ // Fetch cluster network
+ networkClientRecorder.GetNetwork(clusterNetworkID).Return(&networks.Network{
+ ID: clusterNetworkID,
+ Name: "cluster-network",
+ }, nil)
+
+ // Fetching cluster subnets
+ networkClientRecorder.ListSubnet(subnets.ListOpts{
+ NetworkID: clusterNetworkID,
+ }).Return([]subnets.Subnet{
+ {
+ ID: clusterSubnetID,
+ Name: "cluster-subnet",
+ CIDR: "192.168.0.0/24",
+ },
+ }, nil)
+
+ err = reconcileNetworkComponents(scope, capiCluster, testCluster)
+ Expect(err).To(BeNil())
+
+ // Verify API endpoint was set with fixed IP
+ Expect(testCluster.Spec.ControlPlaneEndpoint).ToNot(BeNil())
+ Expect(testCluster.Spec.ControlPlaneEndpoint.Host).To(Equal(fixedIP))
+ Expect(testCluster.Spec.ControlPlaneEndpoint.Port).To(Equal(int32(6443)))
+
+ // Verify conditions are set correctly
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.NetworkReadyCondition)).To(BeTrue())
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.SecurityGroupsReadyCondition)).To(BeTrue())
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.APIEndpointReadyCondition)).To(BeTrue())
+ })
+
+ It("should set NetworkReadyCondition to False when network lookup fails", func() {
+ const clusterNetworkID = "6c90b532-7ba0-418a-a276-5ae55060b5b0"
+
+ testCluster.SetName("network-lookup-failure")
+ testCluster.Spec = infrav1.OpenStackClusterSpec{
+ IdentityRef: infrav1.OpenStackIdentityReference{
+ Name: "test-creds",
+ CloudName: "openstack",
+ },
+ Network: &infrav1.NetworkParam{
+ ID: ptr.To(clusterNetworkID),
+ },
+ DisableExternalNetwork: ptr.To(true),
+ DisableAPIServerFloatingIP: ptr.To(true),
+ APIServerFixedIP: ptr.To("192.168.0.10"),
+ }
+ err := k8sClient.Create(ctx, testCluster)
+ Expect(err).To(BeNil())
+ err = k8sClient.Create(ctx, capiCluster)
+ Expect(err).To(BeNil())
+
+ log := GinkgoLogr
+ clientScope, err := mockScopeFactory.NewClientScopeFromObject(ctx, k8sClient, nil, log, testCluster)
+ Expect(err).To(BeNil())
+ scope := scope.NewWithLogger(clientScope, log)
+
+ networkClientRecorder := mockScopeFactory.NetworkClient.EXPECT()
+
+ // Simulate network lookup failure
+ networkClientRecorder.GetNetwork(clusterNetworkID).Return(nil, fmt.Errorf("unable to get network"))
+
+ err = reconcileNetworkComponents(scope, capiCluster, testCluster)
+ Expect(err).ToNot(BeNil())
+ Expect(err.Error()).To(ContainSubstring("error fetching cluster network"))
+
+ // Verify NetworkReadyCondition is set to False
+ Expect(v1beta1conditions.IsFalse(testCluster, infrav1.NetworkReadyCondition)).To(BeTrue())
+ condition := v1beta1conditions.Get(testCluster, infrav1.NetworkReadyCondition)
+ Expect(condition).ToNot(BeNil())
+ Expect(condition.Reason).To(Equal(infrav1.OpenStackErrorReason))
+ Expect(condition.Severity).To(Equal(clusterv1beta1.ConditionSeverityError))
+ Expect(condition.Message).To(ContainSubstring("Failed to find network"))
+ })
+
+ It("should set NetworkReadyCondition to False when subnet lookup fails", func() {
+ const clusterNetworkID = "6c90b532-7ba0-418a-a276-5ae55060b5b0"
+
+ testCluster.SetName("subnet-lookup-failure")
+ testCluster.Spec = infrav1.OpenStackClusterSpec{
+ IdentityRef: infrav1.OpenStackIdentityReference{
+ Name: "test-creds",
+ CloudName: "openstack",
+ },
+ Network: &infrav1.NetworkParam{
+ ID: ptr.To(clusterNetworkID),
+ },
+ DisableExternalNetwork: ptr.To(true),
+ DisableAPIServerFloatingIP: ptr.To(true),
+ APIServerFixedIP: ptr.To("192.168.0.10"),
+ }
+ err := k8sClient.Create(ctx, testCluster)
+ Expect(err).To(BeNil())
+ err = k8sClient.Create(ctx, capiCluster)
+ Expect(err).To(BeNil())
+
+ log := GinkgoLogr
+ clientScope, err := mockScopeFactory.NewClientScopeFromObject(ctx, k8sClient, nil, log, testCluster)
+ Expect(err).To(BeNil())
+ scope := scope.NewWithLogger(clientScope, log)
+
+ networkClientRecorder := mockScopeFactory.NetworkClient.EXPECT()
+
+ // Network lookup succeeds
+ networkClientRecorder.GetNetwork(clusterNetworkID).Return(&networks.Network{
+ ID: clusterNetworkID,
+ Name: "cluster-network",
+ }, nil)
+
+ // Subnet list lookup fails
+ networkClientRecorder.ListSubnet(subnets.ListOpts{
+ NetworkID: clusterNetworkID,
+ }).Return(nil, fmt.Errorf("failed to list subnets"))
+
+ err = reconcileNetworkComponents(scope, capiCluster, testCluster)
+ Expect(err).ToNot(BeNil())
+
+ // Verify NetworkReadyCondition is set to False
+ Expect(v1beta1conditions.IsFalse(testCluster, infrav1.NetworkReadyCondition)).To(BeTrue())
+ condition := v1beta1conditions.Get(testCluster, infrav1.NetworkReadyCondition)
+ Expect(condition).ToNot(BeNil())
+ Expect(condition.Reason).To(Equal(infrav1.OpenStackErrorReason))
+ Expect(condition.Severity).To(Equal(clusterv1beta1.ConditionSeverityError))
+ })
+
+ It("should set RouterReadyCondition to False when router lookup fails", func() {
+ const clusterNetworkID = "6c90b532-7ba0-418a-a276-5ae55060b5b0"
+ const clusterSubnetID = "cad5a91a-36de-4388-823b-b0cc82cadfdc"
+ const clusterRouterID = "a0e2b0a5-4d2f-4e8d-9a1c-6b3e7f8c9d0e"
+
+ testCluster.SetName("router-lookup-failure")
+ testCluster.Spec = infrav1.OpenStackClusterSpec{
+ IdentityRef: infrav1.OpenStackIdentityReference{
+ Name: "test-creds",
+ CloudName: "openstack",
+ },
+ Network: &infrav1.NetworkParam{
+ ID: ptr.To(clusterNetworkID),
+ },
+ Router: &infrav1.RouterParam{
+ ID: ptr.To(clusterRouterID),
+ },
+ DisableExternalNetwork: ptr.To(true),
+ DisableAPIServerFloatingIP: ptr.To(true),
+ APIServerFixedIP: ptr.To("192.168.0.10"),
+ }
+ err := k8sClient.Create(ctx, testCluster)
+ Expect(err).To(BeNil())
+ err = k8sClient.Create(ctx, capiCluster)
+ Expect(err).To(BeNil())
+
+ log := GinkgoLogr
+ clientScope, err := mockScopeFactory.NewClientScopeFromObject(ctx, k8sClient, nil, log, testCluster)
+ Expect(err).To(BeNil())
+ scope := scope.NewWithLogger(clientScope, log)
+
+ networkClientRecorder := mockScopeFactory.NetworkClient.EXPECT()
+
+ // Network lookup succeeds
+ networkClientRecorder.GetNetwork(clusterNetworkID).Return(&networks.Network{
+ ID: clusterNetworkID,
+ Name: "cluster-network",
+ }, nil)
+
+ // Subnet lookup succeeds
+ networkClientRecorder.ListSubnet(subnets.ListOpts{
+ NetworkID: clusterNetworkID,
+ }).Return([]subnets.Subnet{
+ {
+ ID: clusterSubnetID,
+ Name: "cluster-subnet",
+ CIDR: "192.168.0.0/24",
+ },
+ }, nil)
+
+ // Router lookup fails
+ networkClientRecorder.GetRouter(clusterRouterID).Return(nil, fmt.Errorf("unable to get router"))
+
+ err = reconcileNetworkComponents(scope, capiCluster, testCluster)
+ Expect(err).ToNot(BeNil())
+ Expect(err.Error()).To(ContainSubstring("error fetching cluster router"))
+
+ // Verify RouterReadyCondition is set to False
+ Expect(v1beta1conditions.IsFalse(testCluster, infrav1.RouterReadyCondition)).To(BeTrue())
+ condition := v1beta1conditions.Get(testCluster, infrav1.RouterReadyCondition)
+ Expect(condition).ToNot(BeNil())
+ Expect(condition.Reason).To(Equal(infrav1.OpenStackErrorReason))
+ Expect(condition.Severity).To(Equal(clusterv1beta1.ConditionSeverityError))
+ Expect(condition.Message).To(ContainSubstring("Failed to find router"))
+
+ // NetworkReadyCondition should still be True since network succeeded
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.NetworkReadyCondition)).To(BeTrue())
+ })
+
+ It("should set SecurityGroupsReadyCondition to False when security group reconciliation fails", func() {
+ const clusterNetworkID = "6c90b532-7ba0-418a-a276-5ae55060b5b0"
+ const clusterSubnetID = "cad5a91a-36de-4388-823b-b0cc82cadfdc"
+
+ testCluster.SetName("security-group-failure")
+ testCluster.Spec = infrav1.OpenStackClusterSpec{
+ IdentityRef: infrav1.OpenStackIdentityReference{
+ Name: "test-creds",
+ CloudName: "openstack",
+ },
+ Network: &infrav1.NetworkParam{
+ ID: ptr.To(clusterNetworkID),
+ },
+ DisableExternalNetwork: ptr.To(true),
+ DisableAPIServerFloatingIP: ptr.To(true),
+ APIServerFixedIP: ptr.To("192.168.0.10"),
+ ManagedSecurityGroups: &infrav1.ManagedSecurityGroups{
+ AllNodesSecurityGroupRules: []infrav1.SecurityGroupRuleSpec{
+ {
+ Direction: "ingress",
+ Protocol: ptr.To("tcp"),
+ RemoteManagedGroups: []infrav1.ManagedSecurityGroupName{
+ "worker",
+ },
+ },
+ },
+ },
+ }
+ err := k8sClient.Create(ctx, testCluster)
+ Expect(err).To(BeNil())
+ err = k8sClient.Create(ctx, capiCluster)
+ Expect(err).To(BeNil())
+
+ log := GinkgoLogr
+ clientScope, err := mockScopeFactory.NewClientScopeFromObject(ctx, k8sClient, nil, log, testCluster)
+ Expect(err).To(BeNil())
+ scope := scope.NewWithLogger(clientScope, log)
+
+ networkClientRecorder := mockScopeFactory.NetworkClient.EXPECT()
+
+ // Network lookup succeeds
+ networkClientRecorder.GetNetwork(clusterNetworkID).Return(&networks.Network{
+ ID: clusterNetworkID,
+ Name: "cluster-network",
+ }, nil)
+
+ // Subnet lookup succeeds
+ networkClientRecorder.ListSubnet(subnets.ListOpts{
+ NetworkID: clusterNetworkID,
+ }).Return([]subnets.Subnet{
+ {
+ ID: clusterSubnetID,
+ Name: "cluster-subnet",
+ CIDR: "192.168.0.0/24",
+ },
+ }, nil)
+
+ // Security group creation fails - this will trigger an error in getOrCreateSecurityGroup
+ networkClientRecorder.ListSecGroup(gomock.Any()).Return([]groups.SecGroup{}, nil).AnyTimes()
+ networkClientRecorder.CreateSecGroup(gomock.Any()).Return(nil, fmt.Errorf("quota exceeded")).AnyTimes()
+
+ err = reconcileNetworkComponents(scope, capiCluster, testCluster)
+ Expect(err).ToNot(BeNil())
+ Expect(err.Error()).To(ContainSubstring("failed to reconcile security groups"))
+
+ // Verify SecurityGroupsReadyCondition is set to False
+ Expect(v1beta1conditions.IsFalse(testCluster, infrav1.SecurityGroupsReadyCondition)).To(BeTrue())
+ condition := v1beta1conditions.Get(testCluster, infrav1.SecurityGroupsReadyCondition)
+ Expect(condition).ToNot(BeNil())
+ Expect(condition.Reason).To(Equal(infrav1.SecurityGroupReconcileFailedReason))
+ Expect(condition.Severity).To(Equal(clusterv1beta1.ConditionSeverityError))
+ Expect(condition.Message).To(ContainSubstring("Failed to reconcile security groups"))
+
+ // NetworkReadyCondition should still be True since network succeeded
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.NetworkReadyCondition)).To(BeTrue())
+ })
+
+ It("should set APIEndpointReadyCondition to False when floating IP creation fails", func() {
+ const externalNetworkID = "a42211a2-4d2c-426f-9413-830e4b4abbbc"
+ const clusterNetworkID = "6c90b532-7ba0-418a-a276-5ae55060b5b0"
+ const clusterSubnetID = "cad5a91a-36de-4388-823b-b0cc82cadfdc"
+
+ testCluster.SetName("floating-ip-failure")
+ testCluster.Spec = infrav1.OpenStackClusterSpec{
+ IdentityRef: infrav1.OpenStackIdentityReference{
+ Name: "test-creds",
+ CloudName: "openstack",
+ },
+ ExternalNetwork: &infrav1.NetworkParam{
+ ID: ptr.To(externalNetworkID),
+ },
+ Network: &infrav1.NetworkParam{
+ ID: ptr.To(clusterNetworkID),
+ },
+ // When DisableAPIServerFloatingIP is not set and external network is configured,
+ // a floating IP should be created for the API server
+ }
+ err := k8sClient.Create(ctx, testCluster)
+ Expect(err).To(BeNil())
+ err = k8sClient.Create(ctx, capiCluster)
+ Expect(err).To(BeNil())
+
+ log := GinkgoLogr
+ clientScope, err := mockScopeFactory.NewClientScopeFromObject(ctx, k8sClient, nil, log, testCluster)
+ Expect(err).To(BeNil())
+ scope := scope.NewWithLogger(clientScope, log)
+
+ networkClientRecorder := mockScopeFactory.NetworkClient.EXPECT()
+
+ // Fetch external network
+ networkClientRecorder.GetNetwork(externalNetworkID).Return(&networks.Network{
+ ID: externalNetworkID,
+ Name: "external-network",
+ }, nil)
+
+ // Fetch cluster network
+ networkClientRecorder.GetNetwork(clusterNetworkID).Return(&networks.Network{
+ ID: clusterNetworkID,
+ Name: "cluster-network",
+ }, nil)
+
+ // Fetching cluster subnets
+ networkClientRecorder.ListSubnet(subnets.ListOpts{
+ NetworkID: clusterNetworkID,
+ }).Return([]subnets.Subnet{
+ {
+ ID: clusterSubnetID,
+ Name: "cluster-subnet",
+ CIDR: "192.168.0.0/24",
+ },
+ }, nil)
+
+ // Mock floating IP creation failure
+ networkClientRecorder.CreateFloatingIP(gomock.Any()).Return(nil, fmt.Errorf("quota exceeded"))
+
+ err = reconcileNetworkComponents(scope, capiCluster, testCluster)
+ Expect(err).ToNot(BeNil())
+
+ // Verify APIEndpointReadyCondition is set to False
+ Expect(v1beta1conditions.IsFalse(testCluster, infrav1.APIEndpointReadyCondition)).To(BeTrue())
+ condition := v1beta1conditions.Get(testCluster, infrav1.APIEndpointReadyCondition)
+ Expect(condition).ToNot(BeNil())
+ Expect(condition.Reason).To(Equal(infrav1.APIEndpointConfigFailedReason))
+ Expect(condition.Severity).To(Equal(clusterv1beta1.ConditionSeverityError))
+ Expect(condition.Message).To(ContainSubstring("Failed to reconcile control plane endpoint"))
+
+ // NetworkReadyCondition and SecurityGroupsReadyCondition should still be True
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.NetworkReadyCondition)).To(BeTrue())
+ Expect(v1beta1conditions.IsTrue(testCluster, infrav1.SecurityGroupsReadyCondition)).To(BeTrue())
})
})
diff --git a/controllers/openstackmachine_controller.go b/controllers/openstackmachine_controller.go
index 725b24b11..325f06d56 100644
--- a/controllers/openstackmachine_controller.go
+++ b/controllers/openstackmachine_controller.go
@@ -382,6 +382,7 @@ func (r *OpenStackMachineReconciler) reconcileNormal(ctx context.Context, scope
if instanceStatus == nil {
v1beta1conditions.MarkFalse(openStackMachine, infrav1.InstanceReadyCondition, infrav1.InstanceDeletedReason, clusterv1beta1.ConditionSeverityError, infrav1.ServerUnexpectedDeletedMessage)
+ v1beta1conditions.MarkFalse(openStackMachine, clusterv1beta1.ReadyCondition, infrav1.InstanceDeletedReason, clusterv1beta1.ConditionSeverityError, infrav1.ServerUnexpectedDeletedMessage)
openStackMachine.SetFailure(capoerrors.DeprecatedCAPIUpdateMachineError, errors.New(infrav1.ServerUnexpectedDeletedMessage)) //nolint:staticcheck // This error is not used as an error
return ctrl.Result{}, nil
}
@@ -437,6 +438,22 @@ func (r *OpenStackMachineReconciler) reconcileMachineState(scope *scope.WithLogg
openStackMachine.Spec.ProviderID = ptr.To(fmt.Sprintf("openstack://%s/%s", region, *openStackServer.Status.InstanceID))
openStackMachine.Status.InstanceID = openStackServer.Status.InstanceID
openStackMachine.Status.Ready = true
+
+ // Set initialization.provisioned to true when initial infrastructure provisioning is complete.
+ // This field should only be set once and never changed afterward, as per CAPI v1beta2 contract.
+ // We set it here when the machine becomes ACTIVE for the first time.
+ if openStackMachine.Status.Initialization == nil {
+ openStackMachine.Status.Initialization = &infrav1.MachineInitialization{}
+ }
+ if !openStackMachine.Status.Initialization.Provisioned {
+ openStackMachine.Status.Initialization.Provisioned = true
+ scope.Logger().Info("Initial machine infrastructure provisioning completed")
+ }
+
+ // Set the Ready condition to True when infrastructure is ready.
+ // This condition surfaces into Machine's status.conditions[InfrastructureReady].
+ // It reflects the current operational state of the machine infrastructure.
+ v1beta1conditions.MarkTrue(openStackMachine, clusterv1beta1.ReadyCondition)
case infrav1.InstanceStateError:
// If the machine has a NodeRef then it must have been working at some point,
// so the error could be something temporary.
@@ -447,20 +464,24 @@ func (r *OpenStackMachineReconciler) reconcileMachineState(scope *scope.WithLogg
openStackMachine.SetFailure(capoerrors.DeprecatedCAPIUpdateMachineError, err)
}
v1beta1conditions.MarkFalse(openStackMachine, infrav1.InstanceReadyCondition, infrav1.InstanceStateErrorReason, clusterv1beta1.ConditionSeverityError, "")
+ v1beta1conditions.MarkFalse(openStackMachine, clusterv1beta1.ReadyCondition, infrav1.InstanceStateErrorReason, clusterv1beta1.ConditionSeverityError, "Instance is in ERROR state")
return &ctrl.Result{}
case infrav1.InstanceStateDeleted:
// we should avoid further actions for DELETED VM
scope.Logger().Info("Machine instance state is DELETED, no actions")
v1beta1conditions.MarkFalse(openStackMachine, infrav1.InstanceReadyCondition, infrav1.InstanceDeletedReason, clusterv1beta1.ConditionSeverityError, "")
+ v1beta1conditions.MarkFalse(openStackMachine, clusterv1beta1.ReadyCondition, infrav1.InstanceDeletedReason, clusterv1beta1.ConditionSeverityError, "Instance has been deleted")
return &ctrl.Result{}
case infrav1.InstanceStateBuild, infrav1.InstanceStateUndefined:
scope.Logger().Info("Waiting for instance to become ACTIVE", "id", openStackServer.Status.InstanceID, "status", openStackServer.Status.InstanceState)
+ v1beta1conditions.MarkFalse(openStackMachine, clusterv1beta1.ReadyCondition, infrav1.InstanceNotReadyReason, clusterv1beta1.ConditionSeverityInfo, "Instance is building")
return &ctrl.Result{RequeueAfter: waitForBuildingInstanceToReconcile}
default:
// The other state is normal (for example, migrating, shutoff) but we don't want to proceed until it's ACTIVE
// due to potential conflict or unexpected actions
scope.Logger().Info("Waiting for instance to become ACTIVE", "id", openStackServer.Status.InstanceID, "status", openStackServer.Status.InstanceState)
v1beta1conditions.MarkUnknown(openStackMachine, infrav1.InstanceReadyCondition, infrav1.InstanceNotReadyReason, "Instance state is not handled: %v", ptr.Deref(openStackServer.Status.InstanceState, infrav1.InstanceStateUndefined))
+ v1beta1conditions.MarkUnknown(openStackMachine, clusterv1beta1.ReadyCondition, infrav1.InstanceNotReadyReason, "Instance state is: %v", ptr.Deref(openStackServer.Status.InstanceState, infrav1.InstanceStateUndefined))
return &ctrl.Result{RequeueAfter: waitForInstanceBecomeActiveToReconcile}
}
diff --git a/controllers/openstackmachine_controller_test.go b/controllers/openstackmachine_controller_test.go
index 78bf6f693..b9497c8ef 100644
--- a/controllers/openstackmachine_controller_test.go
+++ b/controllers/openstackmachine_controller_test.go
@@ -20,11 +20,17 @@ import (
"reflect"
"testing"
+ "github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
+ clusterv1beta1 "sigs.k8s.io/cluster-api/api/core/v1beta1"
+ clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2"
+ v1beta1conditions "sigs.k8s.io/cluster-api/util/deprecated/v1beta1/conditions"
infrav1alpha1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha1"
infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1"
+ "sigs.k8s.io/cluster-api-provider-openstack/pkg/scope"
)
const (
@@ -42,6 +48,7 @@ const (
flavorName = "test-flavor"
sshKeyName = "test-ssh-key"
failureDomain = "test-failure-domain"
+ testInstanceID = "test-instance-id-12345"
)
func TestOpenStackMachineSpecToOpenStackServerSpec(t *testing.T) {
@@ -428,3 +435,219 @@ func TestGetPortIDs(t *testing.T) {
})
}
}
+
+func TestReconcileMachineState(t *testing.T) {
+ tests := []struct {
+ name string
+ instanceState infrav1.InstanceState
+ machineHasNodeRef bool
+ expectRequeue bool
+ expectedInstanceReadyCondition *clusterv1beta1.Condition
+ expectedReadyCondition *clusterv1beta1.Condition
+ expectInitializationProvisioned bool
+ expectFailureSet bool
+ }{
+ {
+ name: "Instance state ACTIVE sets conditions to True and initialization.provisioned",
+ instanceState: infrav1.InstanceStateActive,
+ expectRequeue: false,
+ expectedInstanceReadyCondition: &clusterv1beta1.Condition{
+ Type: infrav1.InstanceReadyCondition,
+ Status: corev1.ConditionTrue,
+ },
+ expectedReadyCondition: &clusterv1beta1.Condition{
+ Type: clusterv1beta1.ReadyCondition,
+ Status: corev1.ConditionTrue,
+ },
+ expectInitializationProvisioned: true,
+ },
+ {
+ name: "Instance state ERROR sets conditions to False without NodeRef",
+ instanceState: infrav1.InstanceStateError,
+ machineHasNodeRef: false,
+ expectRequeue: true,
+ expectedInstanceReadyCondition: &clusterv1beta1.Condition{
+ Type: infrav1.InstanceReadyCondition,
+ Status: corev1.ConditionFalse,
+ Severity: clusterv1beta1.ConditionSeverityError,
+ Reason: infrav1.InstanceStateErrorReason,
+ },
+ expectedReadyCondition: &clusterv1beta1.Condition{
+ Type: clusterv1beta1.ReadyCondition,
+ Status: corev1.ConditionFalse,
+ Severity: clusterv1beta1.ConditionSeverityError,
+ Reason: infrav1.InstanceStateErrorReason,
+ },
+ expectFailureSet: true,
+ },
+ {
+ name: "Instance state ERROR with NodeRef does not set failure",
+ instanceState: infrav1.InstanceStateError,
+ machineHasNodeRef: true,
+ expectRequeue: true,
+ expectedInstanceReadyCondition: &clusterv1beta1.Condition{
+ Type: infrav1.InstanceReadyCondition,
+ Status: corev1.ConditionFalse,
+ Severity: clusterv1beta1.ConditionSeverityError,
+ Reason: infrav1.InstanceStateErrorReason,
+ },
+ expectedReadyCondition: &clusterv1beta1.Condition{
+ Type: clusterv1beta1.ReadyCondition,
+ Status: corev1.ConditionFalse,
+ Severity: clusterv1beta1.ConditionSeverityError,
+ Reason: infrav1.InstanceStateErrorReason,
+ },
+ expectFailureSet: false,
+ },
+ {
+ name: "Instance state DELETED sets conditions to False",
+ instanceState: infrav1.InstanceStateDeleted,
+ expectRequeue: true,
+ expectedInstanceReadyCondition: &clusterv1beta1.Condition{
+ Type: infrav1.InstanceReadyCondition,
+ Status: corev1.ConditionFalse,
+ Severity: clusterv1beta1.ConditionSeverityError,
+ Reason: infrav1.InstanceDeletedReason,
+ },
+ expectedReadyCondition: &clusterv1beta1.Condition{
+ Type: clusterv1beta1.ReadyCondition,
+ Status: corev1.ConditionFalse,
+ Severity: clusterv1beta1.ConditionSeverityError,
+ Reason: infrav1.InstanceDeletedReason,
+ },
+ },
+ {
+ name: "Instance state BUILD sets ReadyCondition to False",
+ instanceState: infrav1.InstanceStateBuild,
+ expectRequeue: true,
+ expectedReadyCondition: &clusterv1beta1.Condition{
+ Type: clusterv1beta1.ReadyCondition,
+ Status: corev1.ConditionFalse,
+ Severity: clusterv1beta1.ConditionSeverityInfo,
+ Reason: infrav1.InstanceNotReadyReason,
+ },
+ },
+ {
+ name: "Instance state SHUTOFF sets conditions to Unknown",
+ instanceState: infrav1.InstanceStateShutoff,
+ expectRequeue: true,
+ expectedInstanceReadyCondition: &clusterv1beta1.Condition{
+ Type: infrav1.InstanceReadyCondition,
+ Status: corev1.ConditionUnknown,
+ Reason: infrav1.InstanceNotReadyReason,
+ },
+ expectedReadyCondition: &clusterv1beta1.Condition{
+ Type: clusterv1beta1.ReadyCondition,
+ Status: corev1.ConditionUnknown,
+ Reason: infrav1.InstanceNotReadyReason,
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ openStackMachine := &infrav1.OpenStackMachine{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: openStackMachineName,
+ Namespace: namespace,
+ },
+ Spec: infrav1.OpenStackMachineSpec{
+ Flavor: ptr.To(flavorName),
+ Image: infrav1.ImageParam{
+ Filter: &infrav1.ImageFilter{
+ Name: ptr.To("test-image"),
+ },
+ },
+ },
+ }
+
+ machine := &clusterv1.Machine{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-machine",
+ Namespace: namespace,
+ },
+ }
+ if tt.machineHasNodeRef {
+ machine.Status.NodeRef = clusterv1.MachineNodeReference{
+ Name: "test-node",
+ }
+ }
+
+ openStackServer := &infrav1alpha1.OpenStackServer{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: openStackMachineName,
+ Namespace: namespace,
+ },
+ Status: infrav1alpha1.OpenStackServerStatus{
+ InstanceID: ptr.To(testInstanceID),
+ InstanceState: ptr.To(tt.instanceState),
+ },
+ }
+
+ r := &OpenStackMachineReconciler{}
+ result := r.reconcileMachineState(scope.NewWithLogger(nil, logr.Discard()), openStackMachine, machine, openStackServer)
+
+ // Check requeue
+ if tt.expectRequeue && result == nil {
+ t.Errorf("expected requeue result, got nil")
+ }
+ if !tt.expectRequeue && result != nil {
+ t.Errorf("expected no requeue, got %v", result)
+ }
+
+ // Check InstanceReadyCondition
+ if tt.expectedInstanceReadyCondition != nil {
+ condition := v1beta1conditions.Get(openStackMachine, tt.expectedInstanceReadyCondition.Type)
+ if condition == nil {
+ t.Errorf("expected %s condition to be set", tt.expectedInstanceReadyCondition.Type)
+ } else {
+ if condition.Status != tt.expectedInstanceReadyCondition.Status {
+ t.Errorf("expected %s status %s, got %s", tt.expectedInstanceReadyCondition.Type, tt.expectedInstanceReadyCondition.Status, condition.Status)
+ }
+ if tt.expectedInstanceReadyCondition.Reason != "" && condition.Reason != tt.expectedInstanceReadyCondition.Reason {
+ t.Errorf("expected %s reason %s, got %s", tt.expectedInstanceReadyCondition.Type, tt.expectedInstanceReadyCondition.Reason, condition.Reason)
+ }
+ if tt.expectedInstanceReadyCondition.Severity != "" && condition.Severity != tt.expectedInstanceReadyCondition.Severity {
+ t.Errorf("expected %s severity %s, got %s", tt.expectedInstanceReadyCondition.Type, tt.expectedInstanceReadyCondition.Severity, condition.Severity)
+ }
+ }
+ }
+
+ // Check ReadyCondition
+ if tt.expectedReadyCondition != nil {
+ condition := v1beta1conditions.Get(openStackMachine, tt.expectedReadyCondition.Type)
+ if condition == nil {
+ t.Errorf("expected %s condition to be set", tt.expectedReadyCondition.Type)
+ } else {
+ if condition.Status != tt.expectedReadyCondition.Status {
+ t.Errorf("expected %s status %s, got %s", tt.expectedReadyCondition.Type, tt.expectedReadyCondition.Status, condition.Status)
+ }
+ if tt.expectedReadyCondition.Reason != "" && condition.Reason != tt.expectedReadyCondition.Reason {
+ t.Errorf("expected %s reason %s, got %s", tt.expectedReadyCondition.Type, tt.expectedReadyCondition.Reason, condition.Reason)
+ }
+ if tt.expectedReadyCondition.Severity != "" && condition.Severity != tt.expectedReadyCondition.Severity {
+ t.Errorf("expected %s severity %s, got %s", tt.expectedReadyCondition.Type, tt.expectedReadyCondition.Severity, condition.Severity)
+ }
+ }
+ }
+
+ // Check initialization.provisioned
+ if tt.expectInitializationProvisioned {
+ if openStackMachine.Status.Initialization == nil || !openStackMachine.Status.Initialization.Provisioned {
+ t.Errorf("expected Initialization.Provisioned to be true")
+ }
+ }
+
+ // Check failure is set
+ if tt.expectFailureSet {
+ if openStackMachine.Status.FailureReason == nil || openStackMachine.Status.FailureMessage == nil {
+ t.Errorf("expected FailureReason and FailureMessage to be set")
+ }
+ } else {
+ if openStackMachine.Status.FailureReason != nil || openStackMachine.Status.FailureMessage != nil {
+ t.Errorf("expected FailureReason and FailureMessage to not be set")
+ }
+ }
+ })
+ }
+}
diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md
index 70b609090..fd6937198 100644
--- a/docs/book/src/SUMMARY.md
+++ b/docs/book/src/SUMMARY.md
@@ -3,6 +3,8 @@
- [Introduction](./introduction.md)
- [Getting Started](getting-started.md)
- [Configuration](clusteropenstack/configuration.md)
+- [Experimental Features](./experimental-features/experimental-features.md)
+ - [PriorityQueue](./experimental-features/priority-queue.md)
- [Topics](./topics/index.md)
- [external cloud provider](./topics/external-cloud-provider.md)
- [hosted control plane](./topics/hosted-control-plane.md)
diff --git a/docs/book/src/api/v1beta1/api.md b/docs/book/src/api/v1beta1/api.md
index 0eb377d11..7984f01fa 100644
--- a/docs/book/src/api/v1beta1/api.md
+++ b/docs/book/src/api/v1beta1/api.md
@@ -1565,6 +1565,38 @@ availability zone.
+ClusterInitialization
+
+
+(Appears on:
+OpenStackClusterStatus)
+
+
+
ClusterInitialization represents the initialization status of the cluster.
+
+
+
+
+| Field |
+Description |
+
+
+
+
+
+provisioned
+
+bool
+
+ |
+
+(Optional)
+ Provisioned is set to true when the initial provisioning of the cluster infrastructure is completed.
+The value of this field is never updated after provisioning is completed.
+ |
+
+
+
ExternalRouterIPParam
@@ -1956,6 +1988,38 @@ subnet in the list is taken into account.
+MachineInitialization
+
+
+(Appears on:
+OpenStackMachineStatus)
+
+
+
MachineInitialization contains information about the initialization status of the machine.
+
+
+
+
+| Field |
+Description |
+
+
+
+
+
+provisioned
+
+bool
+
+ |
+
+(Optional)
+ Provisioned is set to true when the initial provisioning of the machine infrastructure is completed.
+The value of this field is never updated after provisioning is completed.
+ |
+
+
+
MachineResources
@@ -2672,6 +2736,22 @@ bool
Ready is true when the cluster infrastructure is ready.
+Deprecated: This field is deprecated and will be removed in a future API version.
+Use status.conditions to determine the ready state of the cluster.
+ |
+
+
+
+initialization
+
+
+ClusterInitialization
+
+
+ |
+
+(Optional)
+ Initialization contains information about the initialization status of the cluster.
|
@@ -2824,6 +2904,8 @@ responsible controller itself being critically misconfigured.
Any transient errors that occur during the reconciliation of
OpenStackClusters can be added as events to the OpenStackCluster object
and/or logged in the controller’s output.
+Deprecated: This field is deprecated and will be removed in a future API version.
+Use status.conditions to report failures.
@@ -2849,6 +2931,23 @@ responsible controller itself being critically misconfigured.
Any transient errors that occur during the reconciliation of
OpenStackClusters can be added as events to the OpenStackCluster object
and/or logged in the controller’s output.
+Deprecated: This field is deprecated and will be removed in a future API version.
+Use status.conditions to report failures.
+
+
+
+
+conditions
+
+sigs.k8s.io/cluster-api/api/core/v1beta1.Conditions
+
+ |
+
+(Optional)
+ Conditions defines current service state of the OpenStackCluster.
+This field surfaces into Cluster’s status.conditions[InfrastructureReady] condition.
+The Ready condition must surface issues during the entire lifecycle of the OpenStackCluster
+(both during initial provisioning and after the initial provisioning is completed).
|
@@ -3597,6 +3696,22 @@ bool
(Optional)
Ready is true when the provider resource is ready.
+Deprecated: This field is deprecated and will be removed in a future API version.
+Use status.conditions to determine the ready state of the machine.
+ |
+
+
+
+initialization
+
+
+MachineInitialization
+
+
+ |
+
+(Optional)
+ Initialization contains information about the initialization status of the machine.
|
@@ -3675,6 +3790,10 @@ sigs.k8s.io/cluster-api-provider-openstack/pkg/utils/errors.DeprecatedCAPIMachin
|
+(Optional)
+ FailureReason explains the reson behind a failure.
+Deprecated: This field is deprecated and will be removed in a future API version.
+Use status.conditions to report failures.
|
@@ -3700,6 +3819,8 @@ responsible controller itself being critically misconfigured.
Any transient errors that occur during the reconciliation of Machines
can be added as events to the Machine object and/or logged in the
controller’s output.
+Deprecated: This field is deprecated and will be removed in a future API version.
+Use status.conditions to report failures.
@@ -3710,6 +3831,11 @@ sigs.k8s.io/cluster-api/api/core/v1beta1.Conditions
|
+(Optional)
+ Conditions defines current service state of the OpenStackMachine.
+This field surfaces into Machine’s status.conditions[InfrastructureReady] condition.
+The Ready condition must surface issues during the entire lifecycle of the OpenStackMachine
+(both during initial provisioning and after the initial provisioning is completed).
|
diff --git a/docs/book/src/experimental-features/experimental-features.md b/docs/book/src/experimental-features/experimental-features.md
new file mode 100644
index 000000000..384e140a7
--- /dev/null
+++ b/docs/book/src/experimental-features/experimental-features.md
@@ -0,0 +1,68 @@
+# Experimental Features
+
+CAPO now ships with experimental features the users can enable.
+
+Currently CAPO has the following experimental features:
+* `PriorityQueue` (env var: `EXP_CAPO_PRIORITY_QUEUE`): [PriorityQueue](./priority-queue.md)
+
+## Enabling Experimental Features for Management Clusters Started with clusterctl
+
+Users can enable/disable features by setting OS environment variables before running `clusterctl init`, e.g.:
+
+```yaml
+export EXP_SOME_FEATURE_NAME=true
+
+clusterctl init --infrastructure openstack
+```
+
+As an alternative to environment variables, it is also possible to set variables in the clusterctl config file located at `$XDG_CONFIG_HOME/cluster-api/clusterctl.yaml`, e.g.:
+```yaml
+# Values for environment variable substitution
+EXP_SOME_FEATURE_NAME: "true"
+```
+In case a variable is defined in both the config file and as an OS environment variable, the environment variable takes precedence.
+For more information on how to set variables for clusterctl, see [clusterctl Configuration File](https://cluster-api.sigs.k8s.io/clusterctl/configuration)
+
+
+## Enabling Experimental Features on Existing Management Clusters
+
+To enable/disable features on existing management clusters, users can edit the controller manager
+deployments, which will then trigger a restart with the requested features. E.g:
+
+```
+kubectl edit -n capo-system deployment.apps/capo-controller-manager
+```
+```
+// Enable/disable available features by modifying Args below.
+spec:
+ template:
+ spec:
+ containers:
+ - args:
+ - --leader-elect
+ - --feature-gates=SomeFeature=true,OtherFeature=false
+```
+
+Similarly, to **validate** if a particular feature is enabled, see the arguments by issuing:
+
+```bash
+kubectl describe -n capo-system deployment.apps/capo-controller-manager
+```
+
+## Enabling Experimental Features for e2e Tests
+
+Features can be enabled by setting them as environmental variables before running e2e tests.
+
+For `ci` this can also be done through updating `./test/e2e/data/e2e_conf.yaml`.
+
+## Enabling Experimental Features on Tilt
+
+On development environments started with `Tilt`, features can be enabled by setting the feature variables in `kustomize_substitutions`, e.g.:
+
+```yaml
+kustomize_substitutions:
+ EXP_CAPO_PRIORITY_QUEUE: 'true'
+```
+
+For more details on setting up a development environment with `tilt`, see [Developing with Tilt](../development/development.md#developing-with-tilt)
+
diff --git a/docs/book/src/experimental-features/priority-queue.md b/docs/book/src/experimental-features/priority-queue.md
new file mode 100644
index 000000000..1bd4945ea
--- /dev/null
+++ b/docs/book/src/experimental-features/priority-queue.md
@@ -0,0 +1,20 @@
+# Priority Queue
+
+> **Note:** PriorityQueue is available in >= 0.14
+
+The `PriorityQueue` feature flag enables the usage of the controller-runtime PriorityQueue.
+
+This feature deprioritizes reconciliation of objects that were not edge-triggered (i.e. due to an create/update etc.) and makes the controller more responsive during full resyncs and controller startups.
+
+More information on controller-runtime PriorityQueue:
+- [release-notes](https://github.com/kubernetes-sigs/controller-runtime/releases/tag/v0.20.0)
+- [design docs](https://github.com/kubernetes-sigs/controller-runtime/pull/3013)
+- [tracking issue](https://github.com/kubernetes-sigs/controller-runtime/issues/2374)
+
+## Enabling Priority Queue
+
+You can enable `PriorityQueue` using the following.
+
+- Environment variable: `EXP_CAPO_PRIORITY_QUEUE=true`
+- clusterctl.yaml variable: `EXP_CAPO_PRIORITY_QUEUE: true`
+- --feature-gates argument: `PriorityQueue=true`
diff --git a/feature/feature.go b/feature/feature.go
new file mode 100644
index 000000000..8da931c1d
--- /dev/null
+++ b/feature/feature.go
@@ -0,0 +1,48 @@
+/*
+Copyright 2022 The Kubernetes Authors.
+
+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 feature handles feature gates.
+package feature
+
+import (
+ "k8s.io/apimachinery/pkg/util/runtime"
+ "k8s.io/component-base/featuregate"
+)
+
+const (
+ // Every capo-specific feature gate should add method here following this template:
+ //
+ // // owner: @username
+ // // alpha: v1.X
+ // MyFeature featuregate.Feature = "MyFeature".
+
+ // PriorityQueue is a feature gate that controls if the controller uses the controller-runtime PriorityQueue
+ // instead of the default queue implementation.
+ //
+ // alpha: v0.14
+ PriorityQueue featuregate.Feature = "PriorityQueue"
+)
+
+func init() {
+ runtime.Must(MutableGates.Add(defaultCAPOFeatureGates))
+}
+
+// defaultCAPOFeatureGates consists of all known capo-specific feature keys.
+// To add a new feature, define a key for it above and add it here.
+var defaultCAPOFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
+ // Every feature should be initiated here:
+ PriorityQueue: {Default: false, PreRelease: featuregate.Alpha},
+}
diff --git a/feature/gates.go b/feature/gates.go
new file mode 100644
index 000000000..0b3c71947
--- /dev/null
+++ b/feature/gates.go
@@ -0,0 +1,33 @@
+/*
+Copyright 2022 The Kubernetes Authors.
+
+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 feature
+
+import (
+ "k8s.io/component-base/featuregate"
+)
+
+var (
+ // MutableGates is a mutable version of DefaultFeatureGate.
+ // Only top-level commands/options setup and the k8s.io/component-base/featuregate/testing package should make use of this.
+ // Tests that need to modify featuregate gates for the duration of their test should use:
+ // defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features., )()
+ MutableGates = featuregate.NewFeatureGate()
+
+ // Gates is a shared global FeatureGate.
+ // Top-level commands/options setup that needs to modify this featuregate gate should use DefaultMutableFeatureGate.
+ Gates featuregate.FeatureGate = MutableGates
+)
diff --git a/go.mod b/go.mod
index 61719d515..8c3f5bd56 100644
--- a/go.mod
+++ b/go.mod
@@ -8,28 +8,28 @@ require (
github.com/google/go-cmp v0.7.0
github.com/google/gofuzz v1.2.0
github.com/google/uuid v1.6.0
- github.com/gophercloud/gophercloud/v2 v2.8.0
+ github.com/gophercloud/gophercloud/v2 v2.9.0
github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1
- github.com/hashicorp/go-version v1.7.0
- github.com/k-orc/openstack-resource-controller/v2 v2.2.0
- github.com/onsi/ginkgo/v2 v2.26.0
+ github.com/hashicorp/go-version v1.8.0
+ github.com/k-orc/openstack-resource-controller/v2 v2.3.0
+ github.com/onsi/ginkgo/v2 v2.27.2
github.com/onsi/gomega v1.38.2
github.com/prometheus/client_golang v1.23.2
github.com/spf13/pflag v1.0.10
go.uber.org/mock v0.6.0
- golang.org/x/crypto v0.43.0
- golang.org/x/text v0.30.0
+ golang.org/x/crypto v0.45.0
+ golang.org/x/text v0.31.0
gopkg.in/ini.v1 v1.67.0
- k8s.io/api v0.33.5
- k8s.io/apiextensions-apiserver v0.33.5
- k8s.io/apimachinery v0.33.5
- k8s.io/client-go v0.33.5
- k8s.io/code-generator v0.33.5
- k8s.io/component-base v0.33.5
+ k8s.io/api v0.33.6
+ k8s.io/apiextensions-apiserver v0.33.6
+ k8s.io/apimachinery v0.33.6
+ k8s.io/client-go v0.33.6
+ k8s.io/code-generator v0.33.6
+ k8s.io/component-base v0.33.6
k8s.io/klog/v2 v2.130.1
k8s.io/kube-openapi v0.0.0-20250610211856-8b98d1ed966a
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
- sigs.k8s.io/cluster-api v1.11.2
+ sigs.k8s.io/cluster-api v1.11.3
sigs.k8s.io/cluster-api/test v1.11.0
sigs.k8s.io/controller-runtime v0.21.0
sigs.k8s.io/structured-merge-diff/v4 v4.7.0
@@ -123,21 +123,20 @@ require (
go.opentelemetry.io/otel/sdk v1.34.0 // indirect
go.opentelemetry.io/otel/trace v1.34.0 // indirect
go.opentelemetry.io/proto/otlp v1.4.0 // indirect
- go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect
- golang.org/x/mod v0.28.0 // indirect
- golang.org/x/net v0.45.0 // indirect
+ golang.org/x/mod v0.29.0 // indirect
+ golang.org/x/net v0.47.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
- golang.org/x/sync v0.17.0 // indirect
- golang.org/x/sys v0.37.0 // indirect
- golang.org/x/term v0.36.0 // indirect
+ golang.org/x/sync v0.18.0 // indirect
+ golang.org/x/sys v0.38.0 // indirect
+ golang.org/x/term v0.37.0 // indirect
golang.org/x/time v0.9.0 // indirect
- golang.org/x/tools v0.37.0 // indirect
- golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect
+ golang.org/x/tools v0.38.0 // indirect
+ golang.org/x/tools/go/expect v0.1.0-deprecated // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
@@ -146,7 +145,7 @@ require (
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
- k8s.io/apiserver v0.33.5 // indirect
+ k8s.io/apiserver v0.33.6 // indirect
k8s.io/cluster-bootstrap v0.33.3 // indirect
k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
diff --git a/go.sum b/go.sum
index 6b72efb5b..d98b91a23 100644
--- a/go.sum
+++ b/go.sum
@@ -46,8 +46,8 @@ github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/coredns/caddy v1.1.1 h1:2eYKZT7i6yxIfGP3qLJoJ7HAsDJqYB+X68g4NYjSrE0=
github.com/coredns/caddy v1.1.1/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4=
-github.com/coredns/corefile-migration v1.0.28 h1:O8YafUREqUcGbRtcJfOmWU6ifcw2HX76I1QvI5xZpsw=
-github.com/coredns/corefile-migration v1.0.28/go.mod h1:56DPqONc3njpVPsdilEnfijCwNGC3/kTJLl7i7SPavY=
+github.com/coredns/corefile-migration v1.0.29 h1:g4cPYMXXDDs9uLE2gFYrJaPBuUAR07eEMGyh9JBE13w=
+github.com/coredns/corefile-migration v1.0.29/go.mod h1:56DPqONc3njpVPsdilEnfijCwNGC3/kTJLl7i7SPavY=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -84,8 +84,8 @@ github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BN
github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
-github.com/gkampitakis/go-snaps v0.5.14 h1:3fAqdB6BCPKHDMHAKRwtPUwYexKtGrNuw8HX/T/4neo=
-github.com/gkampitakis/go-snaps v0.5.14/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
+github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE=
+github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -139,14 +139,14 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/gophercloud/gophercloud/v2 v2.8.0 h1:of2+8tT6+FbEYHfYC8GBu8TXJNsXYSNm9KuvpX7Neqo=
-github.com/gophercloud/gophercloud/v2 v2.8.0/go.mod h1:Ki/ILhYZr/5EPebrPL9Ej+tUg4lqx71/YH2JWVeU+Qk=
+github.com/gophercloud/gophercloud/v2 v2.9.0 h1:Y9OMrwKF9EDERcHFSOTpf/6XGoAI0yOxmsLmQki4LPM=
+github.com/gophercloud/gophercloud/v2 v2.9.0/go.mod h1:Ki/ILhYZr/5EPebrPL9Ej+tUg4lqx71/YH2JWVeU+Qk=
github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1 h1:LS70kbNdqoalMwLXEzP9Xb/cYv9UCzWioXaOynxrytc=
github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1/go.mod h1:qDhuzCRKi90/Yyl/yEqkg8+qABEvK44LhP0D3GWKGtY=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI=
-github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
-github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
+github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@@ -157,8 +157,8 @@ github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE
github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
-github.com/k-orc/openstack-resource-controller/v2 v2.2.0 h1:/1gs5b799BfYHPksxLaAY/DdWg7usn4ogFJEm01GU+Y=
-github.com/k-orc/openstack-resource-controller/v2 v2.2.0/go.mod h1:lwP69Om+l0Xj8wuxVbYgOfDAJI8+8TGu4SH1RiteyCU=
+github.com/k-orc/openstack-resource-controller/v2 v2.3.0 h1:jLI/GH/yzqy6MVzu54dMcimzFmpprBiWBrfHEc9eots=
+github.com/k-orc/openstack-resource-controller/v2 v2.3.0/go.mod h1:3yPrdRJrWHP0qV0IVvLnEWbEQmK7TyWjwUZHiU5dmzA=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
@@ -209,8 +209,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
-github.com/onsi/ginkgo/v2 v2.26.0 h1:1J4Wut1IlYZNEAWIV3ALrT9NfiaGW2cDCJQSFQMs/gE=
-github.com/onsi/ginkgo/v2 v2.26.0/go.mod h1:qhEywmzWTBUY88kfO0BRvX4py7scov9yR+Az2oavUzw=
+github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
+github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
@@ -225,8 +225,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
-github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
@@ -311,8 +309,6 @@ go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg=
go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY=
-go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
-go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
@@ -329,28 +325,28 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
-golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
+golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
+golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
-golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
+golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
+golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
-golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
+golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
+golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
-golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
+golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -361,23 +357,23 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
-golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
+golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
-golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
+golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
+golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
-golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
+golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
+golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
-golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
+golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
+golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/tools/go/expect v0.1.0-deprecated h1:jY2C5HGYR5lqex3gEniOQL0r7Dq5+VGVgY1nudX5lXY=
golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
@@ -410,22 +406,22 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
-k8s.io/api v0.33.5 h1:YR+uhYj05jdRpcksv8kjSliW+v9hwXxn6Cv10aR8Juw=
-k8s.io/api v0.33.5/go.mod h1:2gzShdwXKT5yPGiqrTrn/U/nLZ7ZyT4WuAj3XGDVgVs=
-k8s.io/apiextensions-apiserver v0.33.5 h1:93NZh6rmrcamX/tfv/dZrTsMiQX69ufANmDcKPEgSeA=
-k8s.io/apiextensions-apiserver v0.33.5/go.mod h1:JIbyQnNlu6nQa7b1vgFi51pmlXOk8mdn0WJwUJnz/7U=
-k8s.io/apimachinery v0.33.5 h1:NiT64hln4TQXeYR18/ES39OrNsjGz8NguxsBgp+6QIo=
-k8s.io/apimachinery v0.33.5/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
-k8s.io/apiserver v0.33.5 h1:X1Gy33r4YkRLRqTjGjofk7X1/EjSLEVSJ/A+1qjoj60=
-k8s.io/apiserver v0.33.5/go.mod h1:Q+b5Btbc8x0PqOCeh/xBTesKk+cXQRN+PF2wdrTKDeg=
-k8s.io/client-go v0.33.5 h1:I8BdmQGxInpkMEnJvV6iG7dqzP3JRlpZZlib3OMFc3o=
-k8s.io/client-go v0.33.5/go.mod h1:W8PQP4MxbM4ypgagVE65mUUqK1/ByQkSALF9tzuQ6u0=
+k8s.io/api v0.33.6 h1:9O22ZqwT6CkQ3iboVpvTR5BEWuT3Xm6/8NX6MOLmc38=
+k8s.io/api v0.33.6/go.mod h1:bdon4pRFmRmdsFyltGIoCaPqutN7y//OQ4srD0uy9X0=
+k8s.io/apiextensions-apiserver v0.33.6 h1:S+18aFz53T/iHPgj9iB7bAZmRPeW8b1umsWaRdgj+6U=
+k8s.io/apiextensions-apiserver v0.33.6/go.mod h1:YUhU/Tb0GszjMK5BXb1HpP6+26TQ1xodhfntmZv7RC0=
+k8s.io/apimachinery v0.33.6 h1:Pq+px1i1t7lNgE58dIeBwJh7OWId6pfGD1dYBm/U5HI=
+k8s.io/apimachinery v0.33.6/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
+k8s.io/apiserver v0.33.6 h1:X78/4sciWuKMM6Wlm2z4WMthhJSfsO/vcyjx7J6gEsE=
+k8s.io/apiserver v0.33.6/go.mod h1:5rRMAedHE4Vke25QGuW2kyfPIHC8nmQxNLdvJKAcmIU=
+k8s.io/client-go v0.33.6 h1:icPU5E5XHl/5mdwMUdSxpGtHnnjWzG0MSputTR9odrg=
+k8s.io/client-go v0.33.6/go.mod h1:3z/Cwqdi6/Uo+E17k+OgQi4QfXS0XuIUmfHpK6rQdZU=
k8s.io/cluster-bootstrap v0.33.3 h1:u2NTxJ5CFSBFXaDxLQoOWMly8eni31psVso+caq6uwI=
k8s.io/cluster-bootstrap v0.33.3/go.mod h1:p970f8u8jf273zyQ5raD8WUu2XyAl0SAWOY82o7i/ds=
-k8s.io/code-generator v0.33.5 h1:KwkOvhwAaorjSwF2MQhhdhL3i8bBmAal/TWhX67kdHw=
-k8s.io/code-generator v0.33.5/go.mod h1:Ra+sdZquRakeTGcEnQAPw6BmlZ92IvxwQQTX/XOvOIE=
-k8s.io/component-base v0.33.5 h1:4D3kxjEx1pJRy3WHAZsmX3+LCpmd4ftE+2J4v6naTnQ=
-k8s.io/component-base v0.33.5/go.mod h1:Zma1YjBVuuGxIbspj1vGR3/5blzo2ARf1v0QTtog1to=
+k8s.io/code-generator v0.33.6 h1:1CFzvpqz3r52zjvikYaF5bvYZkKT6brMIMSB7odPaIs=
+k8s.io/code-generator v0.33.6/go.mod h1:ndnG7E7wSIOLMl2GASBRdoRitKGN5fp/qq5nKTmiTh8=
+k8s.io/component-base v0.33.6 h1:a4HGKS3Ax68W1xO1MpVnVybgeJ3OBjvLE9SALoezTZc=
+k8s.io/component-base v0.33.6/go.mod h1:rUo6Mge+6JvNLrhRzhotfGLxI0tyaQXbL2rnth4UEFw=
k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 h1:2OX19X59HxDprNCVrWi6jb7LW1PoqTlYqEq5H2oetog=
k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
@@ -436,8 +432,8 @@ k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
-sigs.k8s.io/cluster-api v1.11.2 h1:uAczaBavU5Y6aDgyoXWtq28k1kalpSZnVItwXHusw1c=
-sigs.k8s.io/cluster-api v1.11.2/go.mod h1:C1gJVAjMXRG+M+djjGYNkoi5kBMhFnOUI9QqZDAtMms=
+sigs.k8s.io/cluster-api v1.11.3 h1:apxfugbP1X8AG7THCM74CTarCOW4H2oOc6hlbm1hY80=
+sigs.k8s.io/cluster-api v1.11.3/go.mod h1:CA471SACi81M8DzRKTlWpHV33G0cfWEj7sC4fALFVok=
sigs.k8s.io/cluster-api/test v1.11.0 h1:dvwMAb5rm4Z7Kj3l9FkeYTWfSthpN0oX3gvUrd8ej24=
sigs.k8s.io/cluster-api/test v1.11.0/go.mod h1:2f489Lp5TKPGVhNL6V3huq8fp6eb23APlY2cLbhuDBU=
sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8=
diff --git a/hack/ci/cloud-init/controller.yaml.tpl b/hack/ci/cloud-init/controller.yaml.tpl
index ace405467..e89a44857 100644
--- a/hack/ci/cloud-init/controller.yaml.tpl
+++ b/hack/ci/cloud-init/controller.yaml.tpl
@@ -10,7 +10,7 @@
# Enable Logging
LOGFILE=/opt/stack/logs/stack.sh.log
VERBOSE=True
- LOG_COLOR=True
+ LOG_COLOR=False
# Host tuning
ENABLE_SYSCTL_MEM_TUNING="True"
@@ -44,7 +44,7 @@
PUBLIC_BRIDGE_MTU=${MTU}
ENABLE_CHASSIS_AS_GW="True"
OVN_DBS_LOG_LEVEL="dbg"
- Q_ML2_PLUGIN_MECHANISM_DRIVERS="ovn,logger"
+ Q_ML2_PLUGIN_MECHANISM_DRIVERS="ovn"
OVN_L3_CREATE_PUBLIC_NETWORK="True"
Q_AGENT="ovn"
diff --git a/hack/ci/cloud-init/worker.yaml.tpl b/hack/ci/cloud-init/worker.yaml.tpl
index 0a34b69a2..63fd48cb8 100644
--- a/hack/ci/cloud-init/worker.yaml.tpl
+++ b/hack/ci/cloud-init/worker.yaml.tpl
@@ -9,7 +9,7 @@
# Enable Logging
LOGFILE=/opt/stack/logs/stack.sh.log
VERBOSE=True
- LOG_COLOR=True
+ LOG_COLOR=False
# Host tuning
ENABLE_SYSCTL_MEM_TUNING="True"
@@ -41,7 +41,7 @@
PUBLIC_BRIDGE_MTU=${MTU}
ENABLE_CHASSIS_AS_GW="False"
OVN_DBS_LOG_LEVEL="dbg"
- Q_ML2_PLUGIN_MECHANISM_DRIVERS="ovn,logger"
+ Q_ML2_PLUGIN_MECHANISM_DRIVERS="ovn"
Q_AGENT="ovn"
# WORKAROUND:
diff --git a/hack/ci/create_devstack.sh b/hack/ci/create_devstack.sh
index 35b66e0ba..8fc40cd96 100755
--- a/hack/ci/create_devstack.sh
+++ b/hack/ci/create_devstack.sh
@@ -31,7 +31,7 @@ source "${scriptdir}/${RESOURCE_TYPE}.sh"
CLUSTER_NAME=${CLUSTER_NAME:-"capo-e2e"}
-OPENSTACK_RELEASE=${OPENSTACK_RELEASE:-"2024.2"}
+OPENSTACK_RELEASE=${OPENSTACK_RELEASE:-"2025.2"}
OPENSTACK_ENABLE_HORIZON=${OPENSTACK_ENABLE_HORIZON:-"false"}
# Devstack will create a provider network using this range
diff --git a/hack/image-patch/kustomization.yaml b/hack/image-patch/kustomization.yaml
index f9032f685..483e205a5 100644
--- a/hack/image-patch/kustomization.yaml
+++ b/hack/image-patch/kustomization.yaml
@@ -1,16 +1,16 @@
apiVersion: kustomize.config.k8s.io/v1beta1
images:
- - name: ""
- newName: ""
- newTag: ""
+- name: ""
+ newName: ""
+ newTag: ""
kind: Kustomization
patchesJson6902:
- - path: pull-policy-patch.yaml
- target:
- group: apps
- kind: Deployment
- name: controller-name
- namespace: namespace
- version: v1
+- path: pull-policy-patch.yaml
+ target:
+ group: apps
+ kind: Deployment
+ name: controller-name
+ namespace: namespace
+ version: v1
resources:
- - source-manifest.yaml
+- source-manifest.yaml
diff --git a/hack/tools/Makefile b/hack/tools/Makefile
index f027449f9..5c20fb30e 100644
--- a/hack/tools/Makefile
+++ b/hack/tools/Makefile
@@ -15,7 +15,7 @@
ROOT_DIR_RELATIVE := ../..
include $(ROOT_DIR_RELATIVE)/common.mk
-GOLANGCI_LINT_VERSION ?= v2.5.0
+GOLANGCI_LINT_VERSION ?= v2.6.2
# GOTESTSUM version without the leading 'v'
GOTESTSUM_VERSION ?= 1.12.0
diff --git a/hack/tools/go.mod b/hack/tools/go.mod
index 940e1a824..df47b6d86 100644
--- a/hack/tools/go.mod
+++ b/hack/tools/go.mod
@@ -6,14 +6,14 @@ require (
github.com/a8m/envsubst v1.4.3
github.com/ahmetb/gen-crd-api-reference-docs v0.3.1-0.20220420215017-3f29e6853552
github.com/itchyny/gojq v0.12.17
- github.com/onsi/ginkgo/v2 v2.26.0
+ github.com/onsi/ginkgo/v2 v2.27.2
go.uber.org/mock v0.6.0
- k8s.io/code-generator v0.33.5
+ k8s.io/code-generator v0.33.6
sigs.k8s.io/cluster-api-provider-openstack v0.0.0
sigs.k8s.io/cluster-api/hack/tools v0.0.0-20250805173327-a7b9f27af519
sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250620151452-b9a9ca01fd37
sigs.k8s.io/controller-tools v0.18.0
- sigs.k8s.io/kustomize/kustomize/v5 v5.7.1
+ sigs.k8s.io/kustomize/kustomize/v5 v5.8.0
)
require (
@@ -63,16 +63,16 @@ require (
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
github.com/google/uuid v1.6.0 // indirect
- github.com/gophercloud/gophercloud/v2 v2.8.0 // indirect
+ github.com/gophercloud/gophercloud/v2 v2.9.0 // indirect
github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect
- github.com/hashicorp/go-version v1.7.0 // indirect
+ github.com/hashicorp/go-version v1.8.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/itchyny/timefmt-go v0.1.6 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
- github.com/k-orc/openstack-resource-controller/v2 v2.2.0 // indirect
+ github.com/k-orc/openstack-resource-controller/v2 v2.3.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
@@ -95,7 +95,7 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/saschagrunert/go-modiff v1.3.5 // indirect
- github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
+ github.com/sergi/go-diff v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.0 // indirect
github.com/spf13/afero v1.12.0 // indirect
@@ -114,23 +114,21 @@ require (
go.opentelemetry.io/otel/sdk v1.36.0 // indirect
go.opentelemetry.io/otel/trace v1.36.0 // indirect
go.opentelemetry.io/proto/otlp v1.4.0 // indirect
- go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
- golang.org/x/crypto v0.43.0 // indirect
+ golang.org/x/crypto v0.45.0 // indirect
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect
- golang.org/x/mod v0.28.0 // indirect
- golang.org/x/net v0.45.0 // indirect
+ golang.org/x/mod v0.29.0 // indirect
+ golang.org/x/net v0.47.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
- golang.org/x/sync v0.17.0 // indirect
- golang.org/x/sys v0.37.0 // indirect
- golang.org/x/term v0.36.0 // indirect
- golang.org/x/text v0.30.0 // indirect
+ golang.org/x/sync v0.18.0 // indirect
+ golang.org/x/sys v0.38.0 // indirect
+ golang.org/x/term v0.37.0 // indirect
+ golang.org/x/text v0.31.0 // indirect
golang.org/x/time v0.12.0 // indirect
- golang.org/x/tools v0.37.0 // indirect
- golang.org/x/tools/go/expect v0.1.0-deprecated // indirect
+ golang.org/x/tools v0.38.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
@@ -141,13 +139,13 @@ require (
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
- k8s.io/api v0.33.5 // indirect
- k8s.io/apiextensions-apiserver v0.33.5 // indirect
- k8s.io/apimachinery v0.34.1 // indirect
- k8s.io/apiserver v0.33.5 // indirect
- k8s.io/client-go v0.33.5 // indirect
+ k8s.io/api v0.33.6 // indirect
+ k8s.io/apiextensions-apiserver v0.33.6 // indirect
+ k8s.io/apimachinery v0.34.2 // indirect
+ k8s.io/apiserver v0.33.6 // indirect
+ k8s.io/client-go v0.33.6 // indirect
k8s.io/cluster-bootstrap v0.33.3 // indirect
- k8s.io/component-base v0.33.5 // indirect
+ k8s.io/component-base v0.33.6 // indirect
k8s.io/gengo v0.0.0-20201203183100-97869a43a9d9 // indirect
k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f // indirect
k8s.io/klog v0.2.0 // indirect
@@ -156,13 +154,13 @@ require (
k8s.io/release v0.16.9 // indirect
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
- sigs.k8s.io/cluster-api v1.11.2 // indirect
+ sigs.k8s.io/cluster-api v1.11.3 // indirect
sigs.k8s.io/controller-runtime v0.21.0 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/kubebuilder/docs/book/utils v0.0.0-20211028165026-57688c578b5d // indirect
- sigs.k8s.io/kustomize/api v0.20.1 // indirect
- sigs.k8s.io/kustomize/cmd/config v0.20.1 // indirect
- sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect
+ sigs.k8s.io/kustomize/api v0.21.0 // indirect
+ sigs.k8s.io/kustomize/cmd/config v0.21.0 // indirect
+ sigs.k8s.io/kustomize/kyaml v0.21.0 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/release-sdk v0.11.0 // indirect
sigs.k8s.io/release-utils v0.8.1 // indirect
diff --git a/hack/tools/go.sum b/hack/tools/go.sum
index 8eb02d51d..12eb630c6 100644
--- a/hack/tools/go.sum
+++ b/hack/tools/go.sum
@@ -49,8 +49,8 @@ github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151X
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/coredns/caddy v1.1.1 h1:2eYKZT7i6yxIfGP3qLJoJ7HAsDJqYB+X68g4NYjSrE0=
github.com/coredns/caddy v1.1.1/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4=
-github.com/coredns/corefile-migration v1.0.28 h1:O8YafUREqUcGbRtcJfOmWU6ifcw2HX76I1QvI5xZpsw=
-github.com/coredns/corefile-migration v1.0.28/go.mod h1:56DPqONc3njpVPsdilEnfijCwNGC3/kTJLl7i7SPavY=
+github.com/coredns/corefile-migration v1.0.29 h1:g4cPYMXXDDs9uLE2gFYrJaPBuUAR07eEMGyh9JBE13w=
+github.com/coredns/corefile-migration v1.0.29/go.mod h1:56DPqONc3njpVPsdilEnfijCwNGC3/kTJLl7i7SPavY=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo=
@@ -91,8 +91,8 @@ github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BN
github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
-github.com/gkampitakis/go-snaps v0.5.14 h1:3fAqdB6BCPKHDMHAKRwtPUwYexKtGrNuw8HX/T/4neo=
-github.com/gkampitakis/go-snaps v0.5.14/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
+github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE=
+github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
@@ -163,14 +163,14 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/gophercloud/gophercloud/v2 v2.8.0 h1:of2+8tT6+FbEYHfYC8GBu8TXJNsXYSNm9KuvpX7Neqo=
-github.com/gophercloud/gophercloud/v2 v2.8.0/go.mod h1:Ki/ILhYZr/5EPebrPL9Ej+tUg4lqx71/YH2JWVeU+Qk=
+github.com/gophercloud/gophercloud/v2 v2.9.0 h1:Y9OMrwKF9EDERcHFSOTpf/6XGoAI0yOxmsLmQki4LPM=
+github.com/gophercloud/gophercloud/v2 v2.9.0/go.mod h1:Ki/ILhYZr/5EPebrPL9Ej+tUg4lqx71/YH2JWVeU+Qk=
github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1 h1:LS70kbNdqoalMwLXEzP9Xb/cYv9UCzWioXaOynxrytc=
github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1/go.mod h1:qDhuzCRKi90/Yyl/yEqkg8+qABEvK44LhP0D3GWKGtY=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI=
-github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
-github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
+github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@@ -187,8 +187,8 @@ github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE
github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
-github.com/k-orc/openstack-resource-controller/v2 v2.2.0 h1:/1gs5b799BfYHPksxLaAY/DdWg7usn4ogFJEm01GU+Y=
-github.com/k-orc/openstack-resource-controller/v2 v2.2.0/go.mod h1:lwP69Om+l0Xj8wuxVbYgOfDAJI8+8TGu4SH1RiteyCU=
+github.com/k-orc/openstack-resource-controller/v2 v2.3.0 h1:jLI/GH/yzqy6MVzu54dMcimzFmpprBiWBrfHEc9eots=
+github.com/k-orc/openstack-resource-controller/v2 v2.3.0/go.mod h1:3yPrdRJrWHP0qV0IVvLnEWbEQmK7TyWjwUZHiU5dmzA=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@@ -243,8 +243,8 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
-github.com/onsi/ginkgo/v2 v2.26.0 h1:1J4Wut1IlYZNEAWIV3ALrT9NfiaGW2cDCJQSFQMs/gE=
-github.com/onsi/ginkgo/v2 v2.26.0/go.mod h1:qhEywmzWTBUY88kfO0BRvX4py7scov9yR+Az2oavUzw=
+github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
+github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
@@ -262,8 +262,6 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
-github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
@@ -275,8 +273,8 @@ github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlT
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
-github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
-github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
+github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
+github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -284,8 +282,8 @@ github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsF
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
github.com/saschagrunert/go-modiff v1.3.5 h1:Wb2KUhCiuTJfhCwGYIwjZOpC++RbY0MTf7J5m1CfQlw=
github.com/saschagrunert/go-modiff v1.3.5/go.mod h1:yWSOFnT8wQIzUMsVflHmkL1qYHk+WLcjzGoeAjqjRXM=
-github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
-github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
+github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
+github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
@@ -362,8 +360,6 @@ go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKr
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg=
go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY=
-go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
-go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
@@ -380,28 +376,28 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
-golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
+golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
+golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
-golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
+golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
+golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
-golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
+golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
+golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
-golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
+golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -413,16 +409,16 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
-golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
+golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
-golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
+golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
+golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
-golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
+golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
+golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -430,8 +426,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
-golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
+golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
+golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/tools/go/expect v0.1.0-deprecated h1:jY2C5HGYR5lqex3gEniOQL0r7Dq5+VGVgY1nudX5lXY=
golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
@@ -469,22 +465,22 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-k8s.io/api v0.33.5 h1:YR+uhYj05jdRpcksv8kjSliW+v9hwXxn6Cv10aR8Juw=
-k8s.io/api v0.33.5/go.mod h1:2gzShdwXKT5yPGiqrTrn/U/nLZ7ZyT4WuAj3XGDVgVs=
-k8s.io/apiextensions-apiserver v0.33.5 h1:93NZh6rmrcamX/tfv/dZrTsMiQX69ufANmDcKPEgSeA=
-k8s.io/apiextensions-apiserver v0.33.5/go.mod h1:JIbyQnNlu6nQa7b1vgFi51pmlXOk8mdn0WJwUJnz/7U=
-k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4=
-k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
-k8s.io/apiserver v0.33.5 h1:X1Gy33r4YkRLRqTjGjofk7X1/EjSLEVSJ/A+1qjoj60=
-k8s.io/apiserver v0.33.5/go.mod h1:Q+b5Btbc8x0PqOCeh/xBTesKk+cXQRN+PF2wdrTKDeg=
-k8s.io/client-go v0.33.5 h1:I8BdmQGxInpkMEnJvV6iG7dqzP3JRlpZZlib3OMFc3o=
-k8s.io/client-go v0.33.5/go.mod h1:W8PQP4MxbM4ypgagVE65mUUqK1/ByQkSALF9tzuQ6u0=
+k8s.io/api v0.33.6 h1:9O22ZqwT6CkQ3iboVpvTR5BEWuT3Xm6/8NX6MOLmc38=
+k8s.io/api v0.33.6/go.mod h1:bdon4pRFmRmdsFyltGIoCaPqutN7y//OQ4srD0uy9X0=
+k8s.io/apiextensions-apiserver v0.33.6 h1:S+18aFz53T/iHPgj9iB7bAZmRPeW8b1umsWaRdgj+6U=
+k8s.io/apiextensions-apiserver v0.33.6/go.mod h1:YUhU/Tb0GszjMK5BXb1HpP6+26TQ1xodhfntmZv7RC0=
+k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4=
+k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
+k8s.io/apiserver v0.33.6 h1:X78/4sciWuKMM6Wlm2z4WMthhJSfsO/vcyjx7J6gEsE=
+k8s.io/apiserver v0.33.6/go.mod h1:5rRMAedHE4Vke25QGuW2kyfPIHC8nmQxNLdvJKAcmIU=
+k8s.io/client-go v0.33.6 h1:icPU5E5XHl/5mdwMUdSxpGtHnnjWzG0MSputTR9odrg=
+k8s.io/client-go v0.33.6/go.mod h1:3z/Cwqdi6/Uo+E17k+OgQi4QfXS0XuIUmfHpK6rQdZU=
k8s.io/cluster-bootstrap v0.33.3 h1:u2NTxJ5CFSBFXaDxLQoOWMly8eni31psVso+caq6uwI=
k8s.io/cluster-bootstrap v0.33.3/go.mod h1:p970f8u8jf273zyQ5raD8WUu2XyAl0SAWOY82o7i/ds=
-k8s.io/code-generator v0.33.5 h1:KwkOvhwAaorjSwF2MQhhdhL3i8bBmAal/TWhX67kdHw=
-k8s.io/code-generator v0.33.5/go.mod h1:Ra+sdZquRakeTGcEnQAPw6BmlZ92IvxwQQTX/XOvOIE=
-k8s.io/component-base v0.33.5 h1:4D3kxjEx1pJRy3WHAZsmX3+LCpmd4ftE+2J4v6naTnQ=
-k8s.io/component-base v0.33.5/go.mod h1:Zma1YjBVuuGxIbspj1vGR3/5blzo2ARf1v0QTtog1to=
+k8s.io/code-generator v0.33.6 h1:1CFzvpqz3r52zjvikYaF5bvYZkKT6brMIMSB7odPaIs=
+k8s.io/code-generator v0.33.6/go.mod h1:ndnG7E7wSIOLMl2GASBRdoRitKGN5fp/qq5nKTmiTh8=
+k8s.io/component-base v0.33.6 h1:a4HGKS3Ax68W1xO1MpVnVybgeJ3OBjvLE9SALoezTZc=
+k8s.io/component-base v0.33.6/go.mod h1:rUo6Mge+6JvNLrhRzhotfGLxI0tyaQXbL2rnth4UEFw=
k8s.io/gengo v0.0.0-20201203183100-97869a43a9d9 h1:1bLA4Agvs1DILmc+q2Bbcqjx6jOHO7YEFA+G+0aTZoc=
k8s.io/gengo v0.0.0-20201203183100-97869a43a9d9/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f h1:SLb+kxmzfA87x4E4brQzB33VBbT2+x7Zq9ROIHmGn9Q=
@@ -502,8 +498,8 @@ k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
-sigs.k8s.io/cluster-api v1.11.2 h1:uAczaBavU5Y6aDgyoXWtq28k1kalpSZnVItwXHusw1c=
-sigs.k8s.io/cluster-api v1.11.2/go.mod h1:C1gJVAjMXRG+M+djjGYNkoi5kBMhFnOUI9QqZDAtMms=
+sigs.k8s.io/cluster-api v1.11.3 h1:apxfugbP1X8AG7THCM74CTarCOW4H2oOc6hlbm1hY80=
+sigs.k8s.io/cluster-api v1.11.3/go.mod h1:CA471SACi81M8DzRKTlWpHV33G0cfWEj7sC4fALFVok=
sigs.k8s.io/cluster-api/hack/tools v0.0.0-20250805173327-a7b9f27af519 h1:WkOO6Fg3tmbuFXqTMFfs80mapaQyBbdIC5p86LIVlBI=
sigs.k8s.io/cluster-api/hack/tools v0.0.0-20250805173327-a7b9f27af519/go.mod h1:/Zjkh19AmjuI4piKJ1fAZW7k0cDZsSoAAcGHKGZJTZU=
sigs.k8s.io/cluster-api/test v1.11.0 h1:dvwMAb5rm4Z7Kj3l9FkeYTWfSthpN0oX3gvUrd8ej24=
@@ -520,14 +516,14 @@ sigs.k8s.io/kind v0.29.0 h1:3TpCsyh908IkXXpcSnsMjWdwdWjIl7o9IMZImZCWFnI=
sigs.k8s.io/kind v0.29.0/go.mod h1:ldWQisw2NYyM6k64o/tkZng/1qQW7OlzcN5a8geJX3o=
sigs.k8s.io/kubebuilder/docs/book/utils v0.0.0-20211028165026-57688c578b5d h1:KLiQzLW3RZJR19+j4pw2h5iioyAyqCkDBEAFdnGa3N8=
sigs.k8s.io/kubebuilder/docs/book/utils v0.0.0-20211028165026-57688c578b5d/go.mod h1:NRdZafr4zSCseLQggdvIMXa7umxf+Q+PJzrj3wFwiGE=
-sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I=
-sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM=
-sigs.k8s.io/kustomize/cmd/config v0.20.1 h1:4APUORmZe2BYrsqgGfEKdd/r7gM6i43egLrUzilpiFo=
-sigs.k8s.io/kustomize/cmd/config v0.20.1/go.mod h1:R7rQ8kxknVlXWVUIbxWtMgu8DCCNVtl8V0KrmeVd/KE=
-sigs.k8s.io/kustomize/kustomize/v5 v5.7.1 h1:sYJsarwy/SDJfjjLMUqwFDGPwzUtMOQ1i1Ed49+XSbw=
-sigs.k8s.io/kustomize/kustomize/v5 v5.7.1/go.mod h1:+5/SrBcJ4agx1SJknGuR/c9thwRSKLxnKoI5BzXFaLU=
-sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78=
-sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po=
+sigs.k8s.io/kustomize/api v0.21.0 h1:I7nry5p8iDJbuRdYS7ez8MUvw7XVNPcIP5GkzzuXIIQ=
+sigs.k8s.io/kustomize/api v0.21.0/go.mod h1:XGVQuR5n2pXKWbzXHweZU683pALGw/AMVO4zU4iS8SE=
+sigs.k8s.io/kustomize/cmd/config v0.21.0 h1:ikLtzcNK9isBqSaXXhAg7LRCTNKdp70z5v/c4Y55DOw=
+sigs.k8s.io/kustomize/cmd/config v0.21.0/go.mod h1:oxa6eRzeLWUcE7M3Rmio29Sfc4KpqGspHur3GjOYqNA=
+sigs.k8s.io/kustomize/kustomize/v5 v5.8.0 h1:CCIJK7z/xJOlkXOaDOcL2jprV53a/eloiL02wg7oJJs=
+sigs.k8s.io/kustomize/kustomize/v5 v5.8.0/go.mod h1:qewGAExYZK9LbPPbnJMPK5HQ8nsdxRzpclIg0qslzDo=
+sigs.k8s.io/kustomize/kyaml v0.21.0 h1:7mQAf3dUwf0wBerWJd8rXhVcnkk5Tvn/q91cGkaP6HQ=
+sigs.k8s.io/kustomize/kyaml v0.21.0/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
diff --git a/main.go b/main.go
index d510e7539..fdefb41c4 100644
--- a/main.go
+++ b/main.go
@@ -26,6 +26,7 @@ import (
"github.com/spf13/pflag"
corev1 "k8s.io/api/core/v1"
+ apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
cliflag "k8s.io/component-base/cli/flag"
@@ -33,13 +34,16 @@ import (
logsv1 "k8s.io/component-base/logs/api/v1"
_ "k8s.io/component-base/logs/json/register"
"k8s.io/klog/v2"
+ "k8s.io/utils/ptr"
clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2"
ipamv1 "sigs.k8s.io/cluster-api/api/ipam/v1beta2"
+ "sigs.k8s.io/cluster-api/controllers/crdmigrator"
"sigs.k8s.io/cluster-api/util/flags"
ctrl "sigs.k8s.io/controller-runtime"
cache "sigs.k8s.io/controller-runtime/pkg/cache"
client "sigs.k8s.io/controller-runtime/pkg/client"
- "sigs.k8s.io/controller-runtime/pkg/client/config"
+ clientconfig "sigs.k8s.io/controller-runtime/pkg/client/config"
+ "sigs.k8s.io/controller-runtime/pkg/config"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/webhook"
@@ -48,6 +52,7 @@ import (
infrav1alpha1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha1"
infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1"
"sigs.k8s.io/cluster-api-provider-openstack/controllers"
+ "sigs.k8s.io/cluster-api-provider-openstack/feature"
"sigs.k8s.io/cluster-api-provider-openstack/pkg/metrics"
"sigs.k8s.io/cluster-api-provider-openstack/pkg/record"
"sigs.k8s.io/cluster-api-provider-openstack/pkg/scope"
@@ -92,10 +97,12 @@ var (
caCertsPath string
showVersion bool
scopeCacheMaxSize int
+ skipCRDMigrationPhases []string
logOptions = logs.NewOptions()
)
func init() {
+ _ = apiextensionsv1.AddToScheme(scheme)
_ = clientgoscheme.AddToScheme(scheme)
_ = clusterv1.AddToScheme(scheme)
_ = ipamv1.AddToScheme(scheme)
@@ -166,12 +173,21 @@ func InitFlags(fs *pflag.FlagSet) {
fs.IntVar(&scopeCacheMaxSize, "scope-cache-max-size", 10, "The maximum credentials count the operator should keep in cache. Setting this value to 0 means no cache.")
+ fs.StringArrayVar(&skipCRDMigrationPhases, "skip-crd-migration-phases", []string{},
+ "List of CRD migration phases to skip. Valid values are: StorageVersionMigration, CleanupManagedFields.")
fs.BoolVar(&showVersion, "version", false, "Show current version and exit.")
+
+ feature.MutableGates.AddFlag(fs)
}
// Add RBAC for the authorized diagnostics endpoint.
// +kubebuilder:rbac:groups=authentication.k8s.io,resources=tokenreviews,verbs=create
// +kubebuilder:rbac:groups=authorization.k8s.io,resources=subjectaccessreviews,verbs=create
+// Setup CRD migrator
+// +kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get;list;watch;update;patch
+// +kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions/status,verbs=get;list;watch;update;patch
+// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=openstackclusters;openstackmachines;openstackmachinetemplates;openstackclustertemplates;openstackfloatingippools;openstackservers;openstackclusteridentities,verbs=get;list;watch;patch;update
+// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=openstackclusters/status;openstackmachines/status;openstackmachinetemplates/status;openstackclustertemplates/status;openstackfloatingippools/status;openstackservers/status;openstackclusteridentities/status,verbs=get;patch;update
func main() {
InitFlags(pflag.CommandLine)
@@ -199,7 +215,7 @@ func main() {
}()
}
- cfg, err := config.GetConfigWithContext(os.Getenv("KUBECONTEXT"))
+ cfg, err := clientconfig.GetConfigWithContext(os.Getenv("KUBECONTEXT"))
if err != nil {
setupLog.Error(err, "unable to get kubeconfig")
os.Exit(1)
@@ -229,6 +245,8 @@ func main() {
}
}
+ setupLog.Info(fmt.Sprintf("Feature gates: %+v\n", feature.Gates))
+
mgr, err := ctrl.NewManager(cfg, ctrl.Options{
Scheme: scheme,
Metrics: *metricsOpts,
@@ -258,6 +276,9 @@ func main() {
),
HealthProbeBindAddress: healthAddr,
LeaderElectionReleaseOnCancel: true,
+ Controller: config.Controller{
+ UsePriorityQueue: ptr.To[bool](feature.Gates.Enabled(feature.PriorityQueue)),
+ },
})
if err != nil {
setupLog.Error(err, "unable to start manager")
@@ -296,6 +317,43 @@ func setupChecks(mgr ctrl.Manager) {
func setupReconcilers(ctx context.Context, mgr ctrl.Manager, caCerts []byte) {
scopeFactory := scope.NewFactory(scopeCacheMaxSize)
+ crdMigratorConfig := map[client.Object]crdmigrator.ByObjectConfig{
+ &infrav1.OpenStackCluster{}: {
+ UseCache: true,
+ },
+ &infrav1.OpenStackMachine{}: {
+ UseCache: true,
+ },
+ &infrav1.OpenStackMachineTemplate{}: {
+ UseCache: true,
+ },
+ &infrav1.OpenStackClusterTemplate{}: {
+ UseCache: true,
+ },
+ &infrav1alpha1.OpenStackFloatingIPPool{}: {
+ UseCache: true,
+ },
+ &infrav1alpha1.OpenStackServer{}: {
+ UseCache: true,
+ },
+ &infrav1alpha1.OpenStackClusterIdentity{}: {
+ UseCache: true,
+ },
+ }
+ crdMigratorSkipPhases := []crdmigrator.Phase{}
+ for _, p := range skipCRDMigrationPhases {
+ crdMigratorSkipPhases = append(crdMigratorSkipPhases, crdmigrator.Phase(p))
+ }
+ if err := (&crdmigrator.CRDMigrator{
+ Client: mgr.GetClient(),
+ APIReader: mgr.GetAPIReader(),
+ SkipCRDMigrationPhases: crdMigratorSkipPhases,
+ Config: crdMigratorConfig,
+ }).SetupWithManager(ctx, mgr, concurrency(1)); err != nil {
+ setupLog.Error(err, "unable to setup CRD migrator")
+ os.Exit(1)
+ }
+
if err := (&controllers.OpenStackClusterReconciler{
Client: mgr.GetClient(),
Recorder: mgr.GetEventRecorderFor("openstackcluster-controller"),
diff --git a/netlify.toml b/netlify.toml
index b2fcec717..a915db07e 100644
--- a/netlify.toml
+++ b/netlify.toml
@@ -4,7 +4,7 @@ command = "make -C docs/book build"
publish = "docs/book/book"
[build.environment]
-GO_VERSION = "1.24.9"
+GO_VERSION = "1.24.11"
# Standard Netlify redirects
[[redirects]]
diff --git a/pkg/clients/loadbalancer.go b/pkg/clients/loadbalancer.go
index 1c3582396..3cf46350d 100644
--- a/pkg/clients/loadbalancer.go
+++ b/pkg/clients/loadbalancer.go
@@ -51,6 +51,7 @@ type LbClient interface {
DeletePool(id string) error
CreatePoolMember(poolID string, opts pools.CreateMemberOptsBuilder) (*pools.Member, error)
ListPoolMember(poolID string, opts pools.ListMembersOptsBuilder) ([]pools.Member, error)
+ GetPoolMember(poolID string, lbMemberID string) (*pools.Member, error)
DeletePoolMember(poolID string, lbMemberID string) error
CreateMonitor(opts monitors.CreateOptsBuilder) (*monitors.Monitor, error)
ListMonitors(opts monitors.ListOptsBuilder) ([]monitors.Monitor, error)
@@ -213,6 +214,15 @@ func (l lbClient) ListPoolMember(poolID string, opts pools.ListMembersOptsBuilde
return pools.ExtractMembers(allPages)
}
+func (l lbClient) GetPoolMember(poolID string, lbMemberID string) (*pools.Member, error) {
+ mc := metrics.NewMetricPrometheusContext("loadbalancer_member", "get")
+ member, err := pools.GetMember(context.TODO(), l.serviceClient, poolID, lbMemberID).Extract()
+ if mc.ObserveRequest(err) != nil {
+ return nil, fmt.Errorf("error getting lbmember: %s", err)
+ }
+ return member, nil
+}
+
func (l lbClient) DeletePoolMember(poolID string, lbMemberID string) error {
mc := metrics.NewMetricPrometheusContext("loadbalancer_member", "delete")
err := pools.DeleteMember(context.TODO(), l.serviceClient, poolID, lbMemberID).ExtractErr()
diff --git a/pkg/clients/mock/loadbalancer.go b/pkg/clients/mock/loadbalancer.go
index 59024521f..586f78843 100644
--- a/pkg/clients/mock/loadbalancer.go
+++ b/pkg/clients/mock/loadbalancer.go
@@ -251,6 +251,21 @@ func (mr *MockLbClientMockRecorder) GetPool(id any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPool", reflect.TypeOf((*MockLbClient)(nil).GetPool), id)
}
+// GetPoolMember mocks base method.
+func (m *MockLbClient) GetPoolMember(poolID, lbMemberID string) (*pools.Member, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "GetPoolMember", poolID, lbMemberID)
+ ret0, _ := ret[0].(*pools.Member)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// GetPoolMember indicates an expected call of GetPoolMember.
+func (mr *MockLbClientMockRecorder) GetPoolMember(poolID, lbMemberID any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPoolMember", reflect.TypeOf((*MockLbClient)(nil).GetPoolMember), poolID, lbMemberID)
+}
+
// ListListeners mocks base method.
func (m *MockLbClient) ListListeners(opts listeners.ListOptsBuilder) ([]listeners.Listener, error) {
m.ctrl.T.Helper()
diff --git a/pkg/cloud/services/compute/instance.go b/pkg/cloud/services/compute/instance.go
index 255faf6d5..e7389a4d9 100644
--- a/pkg/cloud/services/compute/instance.go
+++ b/pkg/cloud/services/compute/instance.go
@@ -65,8 +65,7 @@ func (s *Service) createInstanceImpl(eventObject runtime.Object, instanceSpec *I
})
}
- instanceCreateTimeout := getTimeout("CLUSTER_API_OPENSTACK_INSTANCE_CREATE_TIMEOUT", timeoutInstanceCreate)
- instanceCreateTimeout *= time.Minute
+ instanceCreateTimeout := getTimeout("CLUSTER_API_OPENSTACK_INSTANCE_CREATE_TIMEOUT", timeoutInstanceCreate, time.Minute)
// Don't set ImageRef on the server if we're booting from volume
var serverImageRef string
@@ -606,14 +605,14 @@ func (s *Service) GetInstanceStatusByName(eventObject runtime.Object, name strin
return nil, nil
}
-func getTimeout(name string, timeout int) time.Duration {
+func getTimeout(name string, timeout int, unit time.Duration) time.Duration {
if v := os.Getenv(name); v != "" {
timeout, err := strconv.Atoi(v)
if err == nil {
- return time.Duration(timeout)
+ return time.Duration(timeout) * unit
}
}
- return time.Duration(timeout)
+ return time.Duration(timeout) * unit
}
// requiresTagging checks if the instanceSpec requires tagging,
diff --git a/pkg/cloud/services/loadbalancer/loadbalancer.go b/pkg/cloud/services/loadbalancer/loadbalancer.go
index efcd79a16..394a9ed0e 100644
--- a/pkg/cloud/services/loadbalancer/loadbalancer.go
+++ b/pkg/cloud/services/loadbalancer/loadbalancer.go
@@ -52,6 +52,7 @@ const (
const (
loadBalancerProvisioningStatusActive = "ACTIVE"
loadBalancerProvisioningStatusPendingDelete = "PENDING_DELETE"
+ poolMemberProvisioningStatusActive = "ACTIVE"
)
// Default values for Monitor, sync with `kubebuilder:default` annotations on APIServerLoadBalancerMonitor object.
@@ -710,11 +711,20 @@ func (s *Service) ReconcileLoadBalancerMember(openStackCluster *infrav1.OpenStac
Tags: openStackCluster.Spec.Tags,
}
+ if openStackCluster.Status.Network.ID != openStackCluster.Status.APIServerLoadBalancer.LoadBalancerNetwork.ID {
+ lbMemberOpts.SubnetID = openStackCluster.Status.Network.Subnets[0].ID
+ }
+
if _, err := s.waitForLoadBalancerActive(lbID); err != nil {
return err
}
- if _, err := s.loadbalancerClient.CreatePoolMember(pool.ID, lbMemberOpts); err != nil {
+ member, err := s.loadbalancerClient.CreatePoolMember(pool.ID, lbMemberOpts)
+ if err != nil {
+ return err
+ }
+
+ if _, err := s.waitForPoolMemberActive(pool.ID, member.ID); err != nil {
return err
}
@@ -928,6 +938,25 @@ func (s *Service) waitForLoadBalancerActive(id string) (*loadbalancers.LoadBalan
return lb, nil
}
+// Possible Pool Member states are documented here: https://docs.openstack.org/api-ref/load-balancer/v2/#prov-status
+func (s *Service) waitForPoolMemberActive(poolID, memberID string) (*pools.Member, error) {
+ var member *pools.Member
+
+ s.scope.Logger().Info("Waiting for pool member", "pool_id", poolID, "member_id", memberID, "targetStatus", "ACTIVE")
+ err := wait.ExponentialBackoff(backoff, func() (bool, error) {
+ var err error
+ member, err = s.loadbalancerClient.GetPoolMember(poolID, memberID)
+ if err != nil {
+ return false, err
+ }
+ return member.ProvisioningStatus == poolMemberProvisioningStatusActive, nil
+ })
+ if err != nil {
+ return nil, err
+ }
+ return member, nil
+}
+
func (s *Service) waitForListener(id, target string) error {
s.scope.Logger().Info("Waiting for load balancer listener", "id", id, "targetStatus", target)
return wait.ExponentialBackoff(backoff, func() (bool, error) {
diff --git a/pkg/cloud/services/loadbalancer/loadbalancer_test.go b/pkg/cloud/services/loadbalancer/loadbalancer_test.go
index 4c92670ba..9a2ec0d61 100644
--- a/pkg/cloud/services/loadbalancer/loadbalancer_test.go
+++ b/pkg/cloud/services/loadbalancer/loadbalancer_test.go
@@ -32,6 +32,7 @@ import (
"github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/providers"
. "github.com/onsi/gomega" //nolint:revive
"go.uber.org/mock/gomock"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
clusterv1beta1 "sigs.k8s.io/cluster-api/api/core/v1beta1"
@@ -926,3 +927,279 @@ func Test_getOrCreateAPILoadBalancer(t *testing.T) {
})
}
}
+
+func Test_ReconcileLoadBalancerMember(t *testing.T) {
+ g := NewWithT(t)
+ mockCtrl := gomock.NewController(t)
+ defer mockCtrl.Finish()
+
+ const (
+ clusterName = "AAAAA"
+ clusterResourceName = "k8s-clusterapi-cluster-AAAAA"
+ memberIP = "10.0.0.1"
+ wrongMemberIP = "10.0.0.20"
+ port = 6443
+ machineName = "machine-1"
+
+ clusterNetID = "aaaaaaaa-bbbb-cccc-dddd-111111111111"
+ subnetID = "aaaaaaaa-bbbb-cccc-dddd-222222222222"
+ lbID = "aaaaaaaa-bbbb-cccc-dddd-333333333333"
+ listenerID = "aaaaaaaa-bbbb-cccc-dddd-444444444444"
+ poolID = "aaaaaaaa-bbbb-cccc-dddd-555555555555"
+ memberID = "aaaaaaaa-bbbb-cccc-dddd-666666666666"
+ lbNetOtherID = "aaaaaaaa-bbbb-cccc-dddd-999999999999"
+ )
+
+ makeCluster := func(provider *string, lbNetworkID string) *infrav1.OpenStackCluster {
+ return &infrav1.OpenStackCluster{
+ Spec: infrav1.OpenStackClusterSpec{
+ APIServerLoadBalancer: &infrav1.APIServerLoadBalancer{
+ Enabled: ptr.To(true),
+ Provider: provider,
+ Network: &infrav1.NetworkParam{
+ ID: &lbNetworkID,
+ },
+ },
+ DisableAPIServerFloatingIP: ptr.To(true),
+ ControlPlaneEndpoint: &clusterv1beta1.APIEndpoint{
+ Host: apiHostname,
+ Port: port,
+ },
+ Tags: []string{"k8s", "clusterapi"},
+ },
+ Status: infrav1.OpenStackClusterStatus{
+ APIServerLoadBalancer: &infrav1.LoadBalancer{
+ ID: lbID,
+ LoadBalancerNetwork: &infrav1.NetworkStatusWithSubnets{
+ NetworkStatus: infrav1.NetworkStatus{
+ ID: lbNetworkID,
+ },
+ },
+ },
+ Network: &infrav1.NetworkStatusWithSubnets{
+ NetworkStatus: infrav1.NetworkStatus{
+ ID: clusterNetID,
+ },
+ Subnets: []infrav1.Subnet{
+ {ID: subnetID},
+ },
+ },
+ },
+ }
+ }
+
+ openStackMachine := &infrav1.OpenStackMachine{
+ ObjectMeta: metav1.ObjectMeta{Name: machineName},
+ }
+
+ lbtests := []struct {
+ name string
+ clusterSpec *infrav1.OpenStackCluster
+ expectNetwork func(m *mock.MockNetworkClientMockRecorder)
+ expectLoadBalancer func(m *mock.MockLbClientMockRecorder)
+ wantError error
+ }{
+ {
+ name: "LB member exists, dont create",
+ clusterSpec: makeCluster(nil, clusterNetID),
+ expectNetwork: func(*mock.MockNetworkClientMockRecorder) {},
+ expectLoadBalancer: func(m *mock.MockLbClientMockRecorder) {
+ activeLB := loadbalancers.LoadBalancer{
+ ID: lbID,
+ Name: clusterResourceName + "-kubeapi",
+ ProvisioningStatus: "ACTIVE",
+ }
+ m.GetLoadBalancer(lbID).Return(&activeLB, nil).AnyTimes()
+
+ pool := pools.Pool{
+ ID: poolID,
+ Name: fmt.Sprintf("%s-kubeapi-%d", clusterResourceName, port),
+ }
+ m.ListPools(pools.ListOpts{Name: pool.Name}).Return([]pools.Pool{pool}, nil)
+
+ member := pools.Member{
+ Name: fmt.Sprintf("%s-kubeapi-%d-%s", clusterResourceName, port, machineName),
+ Address: memberIP,
+ }
+ m.ListPoolMember(poolID, pools.ListMembersOpts{Name: member.Name}).Return([]pools.Member{member}, nil)
+ },
+ wantError: nil,
+ },
+ {
+ name: "No LB member, create",
+ clusterSpec: makeCluster(nil, clusterNetID),
+ expectNetwork: func(*mock.MockNetworkClientMockRecorder) {},
+ expectLoadBalancer: func(m *mock.MockLbClientMockRecorder) {
+ activeLB := loadbalancers.LoadBalancer{
+ ID: lbID,
+ Name: clusterResourceName + "-kubeapi",
+ ProvisioningStatus: "ACTIVE",
+ }
+ m.GetLoadBalancer(lbID).Return(&activeLB, nil).AnyTimes()
+
+ pool := pools.Pool{
+ ID: poolID,
+ Name: fmt.Sprintf("%s-kubeapi-%d", clusterResourceName, port),
+ }
+ m.ListPools(pools.ListOpts{Name: pool.Name}).Return([]pools.Pool{pool}, nil)
+
+ poolMemberName := fmt.Sprintf("%s-kubeapi-%d-%s", clusterResourceName, port, machineName)
+ m.ListPoolMember(poolID, pools.ListMembersOpts{Name: poolMemberName}).Return([]pools.Member{}, nil)
+
+ m.CreatePoolMember(
+ poolID,
+ gomock.AssignableToTypeOf(pools.CreateMemberOpts{}),
+ ).DoAndReturn(func(_ string, got pools.CreateMemberOpts) (*pools.Member, error) {
+ // SubnetID must be empty here
+ g.Expect(got.SubnetID).To(Equal(""))
+ return &pools.Member{ID: memberID}, nil
+ })
+
+ pendingMember := pools.Member{
+ ID: memberID,
+ Name: poolMemberName,
+ ProvisioningStatus: "PENDING_CREATE",
+ }
+ m.GetPoolMember(poolID, memberID).Return(&pendingMember, nil)
+
+ activeMember := pendingMember
+ activeMember.ProvisioningStatus = "ACTIVE"
+ m.GetPoolMember(poolID, memberID).Return(&activeMember, nil)
+ },
+ wantError: nil,
+ },
+ {
+ name: "No pool found, return error",
+ clusterSpec: makeCluster(nil, clusterNetID),
+ expectNetwork: func(*mock.MockNetworkClientMockRecorder) {},
+ expectLoadBalancer: func(m *mock.MockLbClientMockRecorder) {
+ activeLB := loadbalancers.LoadBalancer{
+ ID: lbID,
+ Name: clusterResourceName + "-kubeapi",
+ ProvisioningStatus: "ACTIVE",
+ }
+ m.GetLoadBalancer(lbID).Return(&activeLB, nil).AnyTimes()
+
+ poolName := fmt.Sprintf("%s-kubeapi-%d", clusterResourceName, port)
+ m.ListPools(pools.ListOpts{Name: poolName}).Return([]pools.Pool{}, nil)
+ },
+ wantError: errors.New("load balancer pool does not exist yet"),
+ },
+ {
+ name: "LB member with wrong address, re-create",
+ clusterSpec: makeCluster(nil, clusterNetID),
+ expectNetwork: func(*mock.MockNetworkClientMockRecorder) {},
+ expectLoadBalancer: func(m *mock.MockLbClientMockRecorder) {
+ activeLB := loadbalancers.LoadBalancer{
+ ID: lbID,
+ Name: clusterResourceName + "-kubeapi",
+ ProvisioningStatus: "ACTIVE",
+ }
+ m.GetLoadBalancer(lbID).Return(&activeLB, nil).AnyTimes()
+
+ pool := pools.Pool{
+ ID: poolID,
+ Name: fmt.Sprintf("%s-kubeapi-%d", clusterResourceName, port),
+ }
+ m.ListPools(pools.ListOpts{Name: pool.Name}).Return([]pools.Pool{pool}, nil)
+
+ poolMemberName := fmt.Sprintf("%s-kubeapi-%d-%s", clusterResourceName, port, machineName)
+
+ member := pools.Member{
+ Name: poolMemberName,
+ Address: wrongMemberIP,
+ ID: memberID,
+ }
+ m.ListPoolMember(poolID, pools.ListMembersOpts{Name: member.Name}).Return([]pools.Member{member}, nil)
+
+ m.DeletePoolMember(poolID, memberID).Return(nil)
+
+ m.CreatePoolMember(
+ poolID,
+ gomock.AssignableToTypeOf(pools.CreateMemberOpts{}),
+ ).DoAndReturn(func(_ string, got pools.CreateMemberOpts) (*pools.Member, error) {
+ // SubnetID must be empty here
+ g.Expect(got.SubnetID).To(Equal(""))
+ return &pools.Member{ID: memberID}, nil
+ })
+
+ activeMember := pools.Member{
+ ID: memberID,
+ Name: poolMemberName,
+ ProvisioningStatus: "ACTIVE",
+ }
+ m.GetPoolMember(poolID, memberID).Return(&activeMember, nil).AnyTimes()
+ },
+ wantError: nil,
+ },
+ {
+ name: "different LB and cluster networks, set SubnetID on member create",
+ clusterSpec: makeCluster(nil, lbNetOtherID),
+ expectNetwork: func(*mock.MockNetworkClientMockRecorder) {
+ // not used by this path
+ },
+ expectLoadBalancer: func(m *mock.MockLbClientMockRecorder) {
+ // LB initially ACTIVE whenever we wait
+ activeLB := loadbalancers.LoadBalancer{
+ ID: lbID,
+ Name: clusterResourceName + "-kubeapi",
+ ProvisioningStatus: "ACTIVE",
+ }
+ m.GetLoadBalancer(lbID).Return(&activeLB, nil).AnyTimes()
+
+ pool := pools.Pool{
+ ID: poolID,
+ Name: fmt.Sprintf("%s-kubeapi-%d", clusterResourceName, port),
+ }
+ m.ListPools(pools.ListOpts{Name: pool.Name}).Return([]pools.Pool{pool}, nil)
+
+ poolMemberName := fmt.Sprintf("%s-kubeapi-%d-%s", clusterResourceName, port, machineName)
+ m.ListPoolMember(poolID, pools.ListMembersOpts{Name: poolMemberName}).Return([]pools.Member{}, nil)
+
+ // Expect CreatePoolMember; capture opts to assert SubnetID is set
+ m.CreatePoolMember(
+ poolID,
+ gomock.AssignableToTypeOf(pools.CreateMemberOpts{}),
+ ).DoAndReturn(func(_ string, got pools.CreateMemberOpts) (*pools.Member, error) {
+ g.Expect(got.Address).To(Equal(memberIP))
+ g.Expect(got.ProtocolPort).To(Equal(port))
+ expName := fmt.Sprintf("%s-kubeapi-%d-%s", clusterResourceName, port, machineName)
+ g.Expect(got.Name).To(Equal(expName))
+ g.Expect(got.SubnetID).To(Equal(subnetID))
+ // Tags should pass through
+ g.Expect(got.Tags).To(ConsistOf("k8s", "clusterapi"))
+ return &pools.Member{ID: memberID, Address: memberIP, ProtocolPort: port}, nil
+ })
+
+ activeMember := pools.Member{
+ ID: memberID,
+ Name: poolMemberName,
+ ProvisioningStatus: "ACTIVE",
+ }
+ m.GetPoolMember(poolID, memberID).Return(&activeMember, nil).AnyTimes()
+ },
+ wantError: nil,
+ },
+ }
+
+ for _, tt := range lbtests {
+ t.Run(tt.name, func(t *testing.T) {
+ g := NewWithT(t)
+ log := testr.New(t)
+
+ mockScopeFactory := scope.NewMockScopeFactory(mockCtrl, "")
+ lbs, err := NewService(scope.NewWithLogger(mockScopeFactory, log))
+ g.Expect(err).NotTo(HaveOccurred())
+
+ tt.expectNetwork(mockScopeFactory.NetworkClient.EXPECT())
+ tt.expectLoadBalancer(mockScopeFactory.LbClient.EXPECT())
+
+ err = lbs.ReconcileLoadBalancerMember(tt.clusterSpec, openStackMachine, clusterName, memberIP)
+ if tt.wantError != nil {
+ g.Expect(err).To(MatchError(tt.wantError))
+ } else {
+ g.Expect(err).NotTo(HaveOccurred())
+ }
+ })
+ }
+}
diff --git a/pkg/generated/applyconfiguration/api/v1beta1/clusterinitialization.go b/pkg/generated/applyconfiguration/api/v1beta1/clusterinitialization.go
new file mode 100644
index 000000000..4896451b3
--- /dev/null
+++ b/pkg/generated/applyconfiguration/api/v1beta1/clusterinitialization.go
@@ -0,0 +1,39 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+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.
+*/
+
+// Code generated by applyconfiguration-gen. DO NOT EDIT.
+
+package v1beta1
+
+// ClusterInitializationApplyConfiguration represents a declarative configuration of the ClusterInitialization type for use
+// with apply.
+type ClusterInitializationApplyConfiguration struct {
+ Provisioned *bool `json:"provisioned,omitempty"`
+}
+
+// ClusterInitializationApplyConfiguration constructs a declarative configuration of the ClusterInitialization type for use with
+// apply.
+func ClusterInitialization() *ClusterInitializationApplyConfiguration {
+ return &ClusterInitializationApplyConfiguration{}
+}
+
+// WithProvisioned sets the Provisioned field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Provisioned field is set to the value of the last call.
+func (b *ClusterInitializationApplyConfiguration) WithProvisioned(value bool) *ClusterInitializationApplyConfiguration {
+ b.Provisioned = &value
+ return b
+}
diff --git a/pkg/generated/applyconfiguration/api/v1beta1/machineinitialization.go b/pkg/generated/applyconfiguration/api/v1beta1/machineinitialization.go
new file mode 100644
index 000000000..588757505
--- /dev/null
+++ b/pkg/generated/applyconfiguration/api/v1beta1/machineinitialization.go
@@ -0,0 +1,39 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+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.
+*/
+
+// Code generated by applyconfiguration-gen. DO NOT EDIT.
+
+package v1beta1
+
+// MachineInitializationApplyConfiguration represents a declarative configuration of the MachineInitialization type for use
+// with apply.
+type MachineInitializationApplyConfiguration struct {
+ Provisioned *bool `json:"provisioned,omitempty"`
+}
+
+// MachineInitializationApplyConfiguration constructs a declarative configuration of the MachineInitialization type for use with
+// apply.
+func MachineInitialization() *MachineInitializationApplyConfiguration {
+ return &MachineInitializationApplyConfiguration{}
+}
+
+// WithProvisioned sets the Provisioned field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Provisioned field is set to the value of the last call.
+func (b *MachineInitializationApplyConfiguration) WithProvisioned(value bool) *MachineInitializationApplyConfiguration {
+ b.Provisioned = &value
+ return b
+}
diff --git a/pkg/generated/applyconfiguration/api/v1beta1/openstackclusterstatus.go b/pkg/generated/applyconfiguration/api/v1beta1/openstackclusterstatus.go
index 898b9c3f9..9873807f8 100644
--- a/pkg/generated/applyconfiguration/api/v1beta1/openstackclusterstatus.go
+++ b/pkg/generated/applyconfiguration/api/v1beta1/openstackclusterstatus.go
@@ -27,6 +27,7 @@ import (
// with apply.
type OpenStackClusterStatusApplyConfiguration struct {
Ready *bool `json:"ready,omitempty"`
+ Initialization *ClusterInitializationApplyConfiguration `json:"initialization,omitempty"`
Network *NetworkStatusWithSubnetsApplyConfiguration `json:"network,omitempty"`
ExternalNetwork *NetworkStatusApplyConfiguration `json:"externalNetwork,omitempty"`
Router *RouterApplyConfiguration `json:"router,omitempty"`
@@ -38,6 +39,7 @@ type OpenStackClusterStatusApplyConfiguration struct {
Bastion *BastionStatusApplyConfiguration `json:"bastion,omitempty"`
FailureReason *errors.DeprecatedCAPIClusterStatusError `json:"failureReason,omitempty"`
FailureMessage *string `json:"failureMessage,omitempty"`
+ Conditions *corev1beta1.Conditions `json:"conditions,omitempty"`
}
// OpenStackClusterStatusApplyConfiguration constructs a declarative configuration of the OpenStackClusterStatus type for use with
@@ -54,6 +56,14 @@ func (b *OpenStackClusterStatusApplyConfiguration) WithReady(value bool) *OpenSt
return b
}
+// WithInitialization sets the Initialization field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Initialization field is set to the value of the last call.
+func (b *OpenStackClusterStatusApplyConfiguration) WithInitialization(value *ClusterInitializationApplyConfiguration) *OpenStackClusterStatusApplyConfiguration {
+ b.Initialization = value
+ return b
+}
+
// WithNetwork sets the Network field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Network field is set to the value of the last call.
@@ -141,3 +151,11 @@ func (b *OpenStackClusterStatusApplyConfiguration) WithFailureMessage(value stri
b.FailureMessage = &value
return b
}
+
+// WithConditions sets the Conditions field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Conditions field is set to the value of the last call.
+func (b *OpenStackClusterStatusApplyConfiguration) WithConditions(value corev1beta1.Conditions) *OpenStackClusterStatusApplyConfiguration {
+ b.Conditions = &value
+ return b
+}
diff --git a/pkg/generated/applyconfiguration/api/v1beta1/openstackmachinestatus.go b/pkg/generated/applyconfiguration/api/v1beta1/openstackmachinestatus.go
index 03db4df1c..cba090d31 100644
--- a/pkg/generated/applyconfiguration/api/v1beta1/openstackmachinestatus.go
+++ b/pkg/generated/applyconfiguration/api/v1beta1/openstackmachinestatus.go
@@ -29,6 +29,7 @@ import (
// with apply.
type OpenStackMachineStatusApplyConfiguration struct {
Ready *bool `json:"ready,omitempty"`
+ Initialization *MachineInitializationApplyConfiguration `json:"initialization,omitempty"`
InstanceID *string `json:"instanceID,omitempty"`
Addresses []v1.NodeAddress `json:"addresses,omitempty"`
InstanceState *apiv1beta1.InstanceState `json:"instanceState,omitempty"`
@@ -53,6 +54,14 @@ func (b *OpenStackMachineStatusApplyConfiguration) WithReady(value bool) *OpenSt
return b
}
+// WithInitialization sets the Initialization field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Initialization field is set to the value of the last call.
+func (b *OpenStackMachineStatusApplyConfiguration) WithInitialization(value *MachineInitializationApplyConfiguration) *OpenStackMachineStatusApplyConfiguration {
+ b.Initialization = value
+ return b
+}
+
// WithInstanceID sets the InstanceID field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the InstanceID field is set to the value of the last call.
diff --git a/pkg/generated/applyconfiguration/internal/internal.go b/pkg/generated/applyconfiguration/internal/internal.go
index b11934fa5..01d6b118c 100644
--- a/pkg/generated/applyconfiguration/internal/internal.go
+++ b/pkg/generated/applyconfiguration/internal/internal.go
@@ -598,6 +598,12 @@ var schemaYAML = typed.YAMLObject(`types:
- name: type
type:
scalar: string
+- name: io.k8s.sigs.cluster-api-provider-openstack.api.v1beta1.ClusterInitialization
+ map:
+ fields:
+ - name: provisioned
+ type:
+ scalar: boolean
- name: io.k8s.sigs.cluster-api-provider-openstack.api.v1beta1.ExternalRouterIPParam
map:
fields:
@@ -675,6 +681,12 @@ var schemaYAML = typed.YAMLObject(`types:
elementType:
scalar: string
elementRelationship: atomic
+- name: io.k8s.sigs.cluster-api-provider-openstack.api.v1beta1.MachineInitialization
+ map:
+ fields:
+ - name: provisioned
+ type:
+ scalar: boolean
- name: io.k8s.sigs.cluster-api-provider-openstack.api.v1beta1.MachineResources
map:
fields:
@@ -915,6 +927,12 @@ var schemaYAML = typed.YAMLObject(`types:
- name: bastionSecurityGroup
type:
namedType: io.k8s.sigs.cluster-api-provider-openstack.api.v1beta1.SecurityGroupStatus
+ - name: conditions
+ type:
+ list:
+ elementType:
+ namedType: io.k8s.sigs.cluster-api.api.core.v1beta1.Condition
+ elementRelationship: atomic
- name: controlPlaneSecurityGroup
type:
namedType: io.k8s.sigs.cluster-api-provider-openstack.api.v1beta1.SecurityGroupStatus
@@ -932,6 +950,9 @@ var schemaYAML = typed.YAMLObject(`types:
- name: failureReason
type:
scalar: string
+ - name: initialization
+ type:
+ namedType: io.k8s.sigs.cluster-api-provider-openstack.api.v1beta1.ClusterInitialization
- name: network
type:
namedType: io.k8s.sigs.cluster-api-provider-openstack.api.v1beta1.NetworkStatusWithSubnets
@@ -1114,6 +1135,9 @@ var schemaYAML = typed.YAMLObject(`types:
- name: failureReason
type:
scalar: string
+ - name: initialization
+ type:
+ namedType: io.k8s.sigs.cluster-api-provider-openstack.api.v1beta1.MachineInitialization
- name: instanceID
type:
scalar: string
diff --git a/pkg/generated/applyconfiguration/utils.go b/pkg/generated/applyconfiguration/utils.go
index 494dc2afa..e927bafe5 100644
--- a/pkg/generated/applyconfiguration/utils.go
+++ b/pkg/generated/applyconfiguration/utils.go
@@ -72,6 +72,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
return &apiv1beta1.BlockDeviceStorageApplyConfiguration{}
case v1beta1.SchemeGroupVersion.WithKind("BlockDeviceVolume"):
return &apiv1beta1.BlockDeviceVolumeApplyConfiguration{}
+ case v1beta1.SchemeGroupVersion.WithKind("ClusterInitialization"):
+ return &apiv1beta1.ClusterInitializationApplyConfiguration{}
case v1beta1.SchemeGroupVersion.WithKind("ExternalRouterIPParam"):
return &apiv1beta1.ExternalRouterIPParamApplyConfiguration{}
case v1beta1.SchemeGroupVersion.WithKind("FilterByNeutronTags"):
@@ -84,6 +86,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
return &apiv1beta1.ImageParamApplyConfiguration{}
case v1beta1.SchemeGroupVersion.WithKind("LoadBalancer"):
return &apiv1beta1.LoadBalancerApplyConfiguration{}
+ case v1beta1.SchemeGroupVersion.WithKind("MachineInitialization"):
+ return &apiv1beta1.MachineInitializationApplyConfiguration{}
case v1beta1.SchemeGroupVersion.WithKind("MachineResources"):
return &apiv1beta1.MachineResourcesApplyConfiguration{}
case v1beta1.SchemeGroupVersion.WithKind("ManagedSecurityGroups"):
diff --git a/releasenotes/v0.12.6.md b/releasenotes/v0.12.6.md
new file mode 100644
index 000000000..a1d39164d
--- /dev/null
+++ b/releasenotes/v0.12.6.md
@@ -0,0 +1,54 @@
+## Changes since v0.12.5
+## :chart_with_upwards_trend: Overview
+- 16 new commits merged
+- 2 bugs fixed 🐛
+
+## :bug: Bug Fixes
+- Allow changing DNSNameservers in subnet config for OpenstackCluster (#2721)
+- Remove invalid kustomizeconfig from config/webhook (#2852)
+
+## :seedling: Others
+- Add bnallapeta to reviewers (#2849)
+- Add Moshiur as reviewer (#2771)
+- Bump go to 1.24.9 (#2786)
+- Migrate CI to stable/2025.2 (#2813)
+- Refactor generate-codegen (#2762)
+- Remove mdbooth as a maintainer (#2760)
+
+## Dependencies
+
+### Added
+- github.com/gkampitakis/ciinfo: [v0.3.2](https://github.com/gkampitakis/ciinfo/tree/v0.3.2)
+- github.com/gkampitakis/go-diff: [v1.3.2](https://github.com/gkampitakis/go-diff/tree/v1.3.2)
+- github.com/gkampitakis/go-snaps: [v0.5.15](https://github.com/gkampitakis/go-snaps/tree/v0.5.15)
+- github.com/goccy/go-yaml: [v1.18.0](https://github.com/goccy/go-yaml/tree/v1.18.0)
+- github.com/joshdk/go-junit: [v1.0.0](https://github.com/joshdk/go-junit/tree/v1.0.0)
+- github.com/maruel/natural: [v1.1.1](https://github.com/maruel/natural/tree/v1.1.1)
+- github.com/mfridman/tparse: [v0.18.0](https://github.com/mfridman/tparse/tree/v0.18.0)
+- github.com/tidwall/gjson: [v1.18.0](https://github.com/tidwall/gjson/tree/v1.18.0)
+- github.com/tidwall/match: [v1.1.1](https://github.com/tidwall/match/tree/v1.1.1)
+- github.com/tidwall/pretty: [v1.2.1](https://github.com/tidwall/pretty/tree/v1.2.1)
+- github.com/tidwall/sjson: [v1.2.5](https://github.com/tidwall/sjson/tree/v1.2.5)
+
+### Changed
+- github.com/gophercloud/gophercloud/v2: [v2.8.0 → v2.9.0](https://github.com/gophercloud/gophercloud/compare/v2.8.0...v2.9.0)
+- github.com/onsi/ginkgo/v2: [v2.25.2 → v2.27.2](https://github.com/onsi/ginkgo/compare/v2.25.2...v2.27.2)
+- github.com/prometheus/client_golang: [v1.23.0 → v1.23.2](https://github.com/prometheus/client_golang/compare/v1.23.0...v1.23.2)
+- github.com/prometheus/common: [v0.65.0 → v0.66.1](https://github.com/prometheus/common/compare/v0.65.0...v0.66.1)
+- github.com/rogpeppe/go-internal: [v1.12.0 → v1.13.1](https://github.com/rogpeppe/go-internal/compare/v1.12.0...v1.13.1)
+- github.com/spf13/pflag: [v1.0.9 → v1.0.10](https://github.com/spf13/pflag/compare/v1.0.9...v1.0.10)
+- github.com/stretchr/testify: [v1.10.0 → v1.11.1](https://github.com/stretchr/testify/compare/v1.10.0...v1.11.1)
+- google.golang.org/protobuf: v1.36.7 → v1.36.8
+- k8s.io/api: v0.31.12 → v0.31.14
+- k8s.io/apiextensions-apiserver: v0.31.12 → v0.31.14
+- k8s.io/apimachinery: v0.31.12 → v0.31.14
+- k8s.io/apiserver: v0.31.12 → v0.31.14
+- k8s.io/client-go: v0.31.12 → v0.31.14
+- k8s.io/code-generator: v0.31.12 → v0.31.14
+- k8s.io/component-base: v0.31.12 → v0.31.14
+- k8s.io/kms: v0.31.12 → v0.31.14
+
+### Removed
+- github.com/prashantv/gostub: [v1.1.0](https://github.com/prashantv/gostub/tree/v1.1.0)
+
+_Thanks to all our contributors!_ 😊
diff --git a/releasenotes/v0.13.1.md b/releasenotes/v0.13.1.md
new file mode 100644
index 000000000..69728eabd
--- /dev/null
+++ b/releasenotes/v0.13.1.md
@@ -0,0 +1,70 @@
+# Changes since v0.13.0
+
+## :chart_with_upwards_trend: Overview
+
+- 19 new commits merged
+- 2 bugs fixed 🐛
+
+## :bug: Bug Fixes
+
+- Remove invalid kustomizeconfig from config/webhook (#2851)
+- Uplift go to address CVEs (#2765)
+
+## :seedling: Others
+
+- Add bnallapeta to reviewers (#2847)
+- Add Moshiur as reviewer (#2769)
+- Bump go to 1.24.9 (#2788)
+- Bump go version to 1.24.7 (#2719)
+- Migrate CI to stable/2025.2 (#2812)
+- Remove mdbooth as a maintainer (#2759)
+
+## Dependencies
+
+### Added
+
+- github.com/gkampitakis/ciinfo: [v0.3.2](https://github.com/gkampitakis/ciinfo/tree/v0.3.2)
+- github.com/gkampitakis/go-diff: [v1.3.2](https://github.com/gkampitakis/go-diff/tree/v1.3.2)
+- github.com/gkampitakis/go-snaps: [v0.5.15](https://github.com/gkampitakis/go-snaps/tree/v0.5.15)
+- github.com/goccy/go-yaml: [v1.18.0](https://github.com/goccy/go-yaml/tree/v1.18.0)
+- github.com/joshdk/go-junit: [v1.0.0](https://github.com/joshdk/go-junit/tree/v1.0.0)
+- github.com/maruel/natural: [v1.1.1](https://github.com/maruel/natural/tree/v1.1.1)
+- github.com/mfridman/tparse: [v0.18.0](https://github.com/mfridman/tparse/tree/v0.18.0)
+- github.com/tidwall/gjson: [v1.18.0](https://github.com/tidwall/gjson/tree/v1.18.0)
+- github.com/tidwall/match: [v1.1.1](https://github.com/tidwall/match/tree/v1.1.1)
+- github.com/tidwall/pretty: [v1.2.1](https://github.com/tidwall/pretty/tree/v1.2.1)
+- github.com/tidwall/sjson: [v1.2.5](https://github.com/tidwall/sjson/tree/v1.2.5)
+
+### Changed
+
+- github.com/coredns/corefile-migration: [v1.0.27 → v1.0.29](https://github.com/coredns/corefile-migration/compare/v1.0.27...v1.0.29)
+- github.com/gophercloud/gophercloud/v2: [v2.8.0 → v2.9.0](https://github.com/gophercloud/gophercloud/compare/v2.8.0...v2.9.0)
+- github.com/hashicorp/go-version: [v1.7.0 → v1.8.0](https://github.com/hashicorp/go-version/compare/v1.7.0...v1.8.0)
+- github.com/k-orc/openstack-resource-controller/v2: [v2.2.0 → v2.3.0](https://github.com/k-orc/openstack-resource-controller/compare/v2.2.0...v2.3.0)
+- github.com/onsi/ginkgo/v2: [v2.25.3 → v2.27.2](https://github.com/onsi/ginkgo/compare/v2.25.3...v2.27.2)
+- github.com/ulikunitz/xz: [v0.5.12 → v0.5.15](https://github.com/ulikunitz/xz/compare/v0.5.12...v0.5.15)
+- golang.org/x/crypto: v0.42.0 → v0.45.0
+- golang.org/x/mod: v0.27.0 → v0.29.0
+- golang.org/x/net: v0.43.0 → v0.47.0
+- golang.org/x/sync: v0.17.0 → v0.18.0
+- golang.org/x/sys: v0.36.0 → v0.38.0
+- golang.org/x/telemetry: 1a19826 → 078029d
+- golang.org/x/term: v0.35.0 → v0.37.0
+- golang.org/x/text: v0.29.0 → v0.31.0
+- golang.org/x/tools: v0.36.0 → v0.38.0
+- k8s.io/api: v0.33.4 → v0.33.6
+- k8s.io/apiextensions-apiserver: v0.33.4 → v0.33.6
+- k8s.io/apimachinery: v0.33.4 → v0.33.6
+- k8s.io/apiserver: v0.33.4 → v0.33.6
+- k8s.io/client-go: v0.33.4 → v0.33.6
+- k8s.io/code-generator: v0.33.4 → v0.33.6
+- k8s.io/component-base: v0.33.4 → v0.33.6
+- k8s.io/kms: v0.33.4 → v0.33.6
+- sigs.k8s.io/cluster-api/test: v1.11.1 → v1.11.3
+- sigs.k8s.io/cluster-api: v1.11.1 → v1.11.3
+
+### Removed
+
+- github.com/prashantv/gostub: [v1.1.0](https://github.com/prashantv/gostub/tree/v1.1.0)
+
+_Thanks to all our contributors!_ 😊
diff --git a/templates/cluster-template-development.yaml b/templates/cluster-template-topology.yaml
similarity index 86%
rename from templates/cluster-template-development.yaml
rename to templates/cluster-template-topology.yaml
index df62668d3..d83ca03ea 100644
--- a/templates/cluster-template-development.yaml
+++ b/templates/cluster-template-topology.yaml
@@ -1,25 +1,20 @@
-apiVersion: cluster.x-k8s.io/v1beta1
+apiVersion: cluster.x-k8s.io/v1beta2
kind: Cluster
metadata:
name: ${CLUSTER_NAME}
spec:
topology:
- class: dev-test
- version: ${KUBERNETES_VERSION}
+ classRef:
+ name: dev-test
controlPlane:
replicas: ${CONTROL_PLANE_MACHINE_COUNT}
- workers:
- machineDeployments:
- - class: default-worker
- name: md-0
- replicas: ${WORKER_MACHINE_COUNT}
variables:
- name: identityRef
value:
- name: ${CLOUD_CONFIG_SECRET:=dev-test-cloud-config}
cloudName: ${OPENSTACK_CLOUD:=capo-e2e}
- - name: imageName
- value: ${IMAGE_NAME:=flatcar_production}
+ name: ${CLOUD_CONFIG_SECRET:=dev-test-cloud-config}
+ - name: imageRef
+ value: ${IMAGE_REF:=node-image}
- name: addImageVersion
value: ${ADD_IMAGE_VERSION:=false}
- name: injectIgnitionSysext
@@ -32,6 +27,12 @@ spec:
spec:
flavor: ${OPENSTACK_BASTION_FLAVOR:=m1.small}
image:
- filter:
+ imageRef:
name: ${OPENSTACK_BASTION_IMAGE_NAME:=ubuntu-24.04}
sshKeyName: ${OPENSTACK_SSH_KEY_NAME:=""}
+ version: ${KUBERNETES_VERSION}
+ workers:
+ machineDeployments:
+ - class: default-worker
+ name: md-0
+ replicas: ${WORKER_MACHINE_COUNT}
diff --git a/templates/clusterclass-dev-test.yaml b/templates/clusterclass-dev-test.yaml
index eff4f62ae..220d4b066 100644
--- a/templates/clusterclass-dev-test.yaml
+++ b/templates/clusterclass-dev-test.yaml
@@ -5,7 +5,7 @@ metadata:
spec:
controlPlane:
templateRef:
- apiVersion: controlplane.cluster.x-k8s.io/v1beta1
+ apiVersion: controlplane.cluster.x-k8s.io/v1beta2
kind: KubeadmControlPlaneTemplate
name: dev-test-control-plane
machineInfrastructure:
@@ -23,7 +23,7 @@ spec:
- class: default-worker
bootstrap:
templateRef:
- apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+ apiVersion: bootstrap.cluster.x-k8s.io/v1beta2
kind: KubeadmConfigTemplate
name: dev-test-default-worker-bootstraptemplate
infrastructure:
@@ -53,16 +53,22 @@ spec:
type: string
description: |
The base name of the OpenStack image that is used for creating the servers.
- This will be combined with the k8s version to create the full name. E.g. imageName-v1.31.2.
- default: "ubuntu-2404-kube"
- name: addImageVersion
required: false
schema:
openAPIV3Schema:
type: boolean
description: |
- Add a suffix with the Kubernetes version to the imageName. E.g. imageName-v1.32.2.
+ Add a suffix with the Kubernetes version to the imageName or imageRef. E.g. imageName-v1.32.2.
default: true
+ - name: imageRef
+ required: false
+ schema:
+ openAPIV3Schema:
+ type: string
+ description: |
+ The name of the ORC Image object that is used for creating the servers.
+ default: "node-image"
- name: injectIgnitionSysext
required: false
schema:
@@ -109,6 +115,12 @@ spec:
name:
type: string
description: "Name of the image to use for bastion"
+ imageRef:
+ type: object
+ properties:
+ name:
+ type: string
+ description: "Name of the ORC Image to use for bastion"
sshKeyName:
type: string
description: "SSH key pair name for bastion access"
@@ -120,8 +132,9 @@ spec:
type: string
description: "Availability zone for the bastion host"
patches:
- - name: image
- description: "Sets the OpenStack image that is used for creating the servers."
+ - name: imageName
+ description: "Sets the OpenStack image that is used for creating the servers using a name filter."
+ enabledIf: "{{ if .imageName }}true{{ end }}"
definitions:
- selector:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
@@ -129,11 +142,13 @@ spec:
matchResources:
controlPlane: true
jsonPatches:
+ - op: remove
+ path: /spec/template/spec/image/imageRef
- op: add
- path: /spec/template/spec/image/filter/name
+ path: /spec/template/spec/image/filter
valueFrom:
template: |
- {{ .imageName }}{{ if .addImageVersion }}-{{ .builtin.controlPlane.version }}{{ end }}
+ name: {{ .imageName }}{{ if .addImageVersion }}-{{ .builtin.controlPlane.version }}{{ end }}
- selector:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: OpenStackMachineTemplate
@@ -142,11 +157,41 @@ spec:
names:
- default-worker
jsonPatches:
+ - op: remove
+ path: /spec/template/spec/image/imageRef
- op: add
- path: /spec/template/spec/image/filter/name
+ path: /spec/template/spec/image/filter
valueFrom:
template: |
- {{ .imageName }}{{ if .addImageVersion }}-{{ .builtin.machineDeployment.version }}{{ end }}
+ name: {{ .imageName }}{{ if .addImageVersion }}-{{ .builtin.machineDeployment.version }}{{ end }}
+ - name: imageRef
+ description: "Sets the OpenStack image that is used for creating the servers using an ORC Image reference."
+ enabledIf: "{{ if .imageRef }}true{{ end }}"
+ definitions:
+ - selector:
+ apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+ kind: OpenStackMachineTemplate
+ matchResources:
+ controlPlane: true
+ jsonPatches:
+ - op: replace
+ path: /spec/template/spec/image/imageRef/name
+ valueFrom:
+ template: |
+ {{ .imageRef }}{{ if .addImageVersion }}-{{ .builtin.controlPlane.version }}{{ end }}
+ - selector:
+ apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+ kind: OpenStackMachineTemplate
+ matchResources:
+ machineDeploymentClass:
+ names:
+ - default-worker
+ jsonPatches:
+ - op: replace
+ path: /spec/template/spec/image/imageRef/name
+ valueFrom:
+ template: |
+ {{ .imageRef }}{{ if .addImageVersion }}-{{ .builtin.controlPlane.version }}{{ end }}
- name: identityRef
description: "Sets the OpenStack identity reference."
definitions:
@@ -193,7 +238,7 @@ spec:
enabledIf: "{{ .injectIgnitionSysext }}"
definitions:
- selector:
- apiVersion: controlplane.cluster.x-k8s.io/v1beta1
+ apiVersion: controlplane.cluster.x-k8s.io/v1beta2
kind: KubeadmControlPlaneTemplate
matchResources:
controlPlane: true
@@ -211,12 +256,18 @@ spec:
nodeRegistration:
name: $${COREOS_OPENSTACK_HOSTNAME}
kubeletExtraArgs:
- provider-id: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
+ - name: cloud-provider
+ value: external
+ - name: provider-id
+ value: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
joinConfiguration:
nodeRegistration:
name: $${COREOS_OPENSTACK_HOSTNAME}
kubeletExtraArgs:
- provider-id: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
+ - name: cloud-provider
+ value: external
+ - name: provider-id
+ value: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
format: ignition
ignition:
containerLinuxConfig:
@@ -233,16 +284,16 @@ spec:
mode: 0644
contents:
remote:
- url: https://github.com/flatcar/sysext-bakery/releases/download/latest/kubernetes-{{ $minor }}.conf
+ url: https://extensions.flatcar.org/extensions/kubernetes/kubernetes-{{ $minor }}.conf
- path: /etc/sysupdate.d/noop.conf
mode: 0644
contents:
remote:
- url: https://github.com/flatcar/sysext-bakery/releases/download/latest/noop.conf
+ url: https://extensions.flatcar.org/extensions/noop.conf
- path: /opt/extensions/kubernetes/kubernetes-{{ .builtin.controlPlane.version }}-x86-64.raw
contents:
remote:
- url: https://github.com/flatcar/sysext-bakery/releases/download/latest/kubernetes-{{ .builtin.controlPlane.version }}-x86-64.raw
+ url: https://extensions.flatcar.org/extensions/kubernetes-{{ .builtin.controlPlane.version }}-x86-64.raw
systemd:
units:
- name: systemd-sysupdate.service
@@ -277,7 +328,7 @@ spec:
[Service]
EnvironmentFile=/run/metadata/flatcar
- selector:
- apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+ apiVersion: bootstrap.cluster.x-k8s.io/v1beta2
kind: KubeadmConfigTemplate
matchResources:
machineDeploymentClass:
@@ -297,7 +348,10 @@ spec:
nodeRegistration:
name: $${COREOS_OPENSTACK_HOSTNAME}
kubeletExtraArgs:
- provider-id: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
+ - name: cloud-provider
+ value: external
+ - name: provider-id
+ value: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
format: ignition
ignition:
containerLinuxConfig:
@@ -314,16 +368,16 @@ spec:
mode: 0644
contents:
remote:
- url: https://github.com/flatcar/sysext-bakery/releases/download/latest/kubernetes-{{ $minor }}.conf
+ url: https://extensions.flatcar.org/extensions/kubernetes/kubernetes-{{ $minor }}.conf
- path: /etc/sysupdate.d/noop.conf
mode: 0644
contents:
remote:
- url: https://github.com/flatcar/sysext-bakery/releases/download/latest/noop.conf
+ url: https://extensions.flatcar.org/extensions/noop.conf
- path: /opt/extensions/kubernetes/kubernetes-{{ .builtin.machineDeployment.version }}-x86-64.raw
contents:
remote:
- url: https://github.com/flatcar/sysext-bakery/releases/download/latest/kubernetes-{{ .builtin.machineDeployment.version }}-x86-64.raw
+ url: https://extensions.flatcar.org/extensions/kubernetes-{{ .builtin.machineDeployment.version }}-x86-64.raw
systemd:
units:
- name: systemd-sysupdate.service
@@ -365,7 +419,6 @@ metadata:
spec:
template:
spec:
- files: []
joinConfiguration:
nodeRegistration:
kubeletExtraArgs:
@@ -382,6 +435,11 @@ metadata:
spec:
template:
spec:
+ rollout:
+ strategy:
+ type: RollingUpdate
+ rollingUpdate:
+ maxSurge: 1
kubeadmConfigSpec:
clusterConfiguration:
controllerManager:
@@ -454,8 +512,8 @@ spec:
spec:
flavor: ${OPENSTACK_CONTROL_PLANE_MACHINE_FLAVOR:=m1.medium}
image:
- filter:
- name: overridden-by-patch
+ imageRef:
+ name: node-image
sshKeyName: ${OPENSTACK_SSH_KEY_NAME:=""}
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
@@ -467,6 +525,6 @@ spec:
spec:
flavor: ${OPENSTACK_NODE_MACHINE_FLAVOR:=m1.small}
image:
- filter:
- name: overridden-by-patch
+ imageRef:
+ name: node-image
sshKeyName: ${OPENSTACK_SSH_KEY_NAME:=""}
diff --git a/test/e2e/data/e2e_conf.yaml b/test/e2e/data/e2e_conf.yaml
index 9914f0eb0..6f7e63811 100644
--- a/test/e2e/data/e2e_conf.yaml
+++ b/test/e2e/data/e2e_conf.yaml
@@ -171,6 +171,7 @@ providers:
- sourcePath: "./infrastructure-openstack-no-artifact/cluster-template.yaml"
- sourcePath: "./infrastructure-openstack-no-artifact/cluster-template-without-lb.yaml"
- sourcePath: "./infrastructure-openstack-no-artifact/cluster-template-cluster-identity.yaml"
+ - sourcePath: "../../../templates/clusterclass-dev-test.yaml"
replacements:
- old: gcr.io/k8s-staging-capi-openstack/capi-openstack-controller:dev
new: gcr.io/k8s-staging-capi-openstack/capi-openstack-controller:e2e
@@ -207,9 +208,11 @@ providers:
variables:
# used to ensure we deploy to the correct management cluster
KUBE_CONTEXT: "kind-capo-e2e"
- KUBERNETES_VERSION: "v1.33.1"
- KUBERNETES_VERSION_UPGRADE_FROM: "v1.32.5"
- KUBERNETES_VERSION_UPGRADE_TO: "v1.33.1"
+ # Pick a version that has a kind node image available. This is used in clusterctl upgrade tests.
+ KUBERNETES_KIND_VERSION: "v1.34.0"
+ KUBERNETES_VERSION: "v1.34.2"
+ KUBERNETES_VERSION_UPGRADE_FROM: "v1.33.1"
+ KUBERNETES_VERSION_UPGRADE_TO: "v1.34.2"
# NOTE: To see default images run kubeadm config images list (optionally with --kubernetes-version=vX.Y.Z)
ETCD_VERSION_UPGRADE_TO: "3.5.21-0"
COREDNS_VERSION_UPGRADE_TO: "v1.12.0"
@@ -218,6 +221,8 @@ variables:
CNI: "../../data/cni/calico.yaml"
CCM: "../../data/ccm/cloud-controller-manager.yaml"
EXP_CLUSTER_RESOURCE_SET: "true"
+ EXP_CAPO_PRIORITY_QUEUE: "false"
+ IP_FAMILY: "ipv4"
OPENSTACK_BASTION_IMAGE_NAME: "cirros-0.6.1-x86_64-disk"
OPENSTACK_BASTION_IMAGE_URL: https://storage.googleapis.com/artifacts.k8s-staging-capi-openstack.appspot.com/test/cirros/2022-12-05/cirros-0.6.1-x86_64-disk.img
OPENSTACK_BASTION_IMAGE_HASH: 0c839612eb3f2469420f2ccae990827f
@@ -232,10 +237,10 @@ variables:
OPENSTACK_DNS_NAMESERVERS: "8.8.8.8"
OPENSTACK_FAILURE_DOMAIN: "testaz1"
OPENSTACK_FAILURE_DOMAIN_ALT: "testaz2"
- OPENSTACK_IMAGE_NAME: "ubuntu-2404-kube-v1.33.1"
- OPENSTACK_IMAGE_URL: https://storage.googleapis.com/artifacts.k8s-staging-capi-openstack.appspot.com/test/ubuntu/ubuntu-2404-kube-v1.33.1
- OPENSTACK_IMAGE_NAME_UPGRADE_FROM: "ubuntu-2404-kube-v1.32.5"
- OPENSTACK_IMAGE_URL_UPGRADE_FROM: https://storage.googleapis.com/artifacts.k8s-staging-capi-openstack.appspot.com/test/ubuntu/ubuntu-2404-kube-v1.32.5
+ OPENSTACK_IMAGE_NAME: "ubuntu-2404-kube-latest"
+ OPENSTACK_IMAGE_URL: https://storage.googleapis.com/artifacts.k8s-staging-capi-openstack.appspot.com/test/ubuntu/ubuntu-2404-kube-v1.34.2
+ OPENSTACK_IMAGE_NAME_UPGRADE_FROM: "ubuntu-2404-kube-previous"
+ OPENSTACK_IMAGE_URL_UPGRADE_FROM: https://storage.googleapis.com/artifacts.k8s-staging-capi-openstack.appspot.com/test/ubuntu/ubuntu-2404-kube-v1.33.1
OPENSTACK_NODE_MACHINE_FLAVOR: "m1.small"
OPENSTACK_SSH_KEY_NAME: "cluster-api-provider-openstack-sigs-k8s-io"
# The default external network created by devstack
@@ -246,10 +251,11 @@ variables:
E2E_IMAGE_URL: "http://10.0.3.15/capo-e2e-image.tar"
# The default user for SSH connections from bastion to machines
SSH_USER_MACHINE: "ubuntu"
+ CLUSTER_TOPOLOGY: "true"
EXP_KUBEADM_BOOTSTRAP_FORMAT_IGNITION: "true"
# The Flatcar image produced by the image-builder
- OPENSTACK_FLATCAR_IMAGE_NAME: "flatcar-stable-4152.2.3-kube-v1.33.1"
- OPENSTACK_FLATCAR_IMAGE_URL: "https://storage.googleapis.com/artifacts.k8s-staging-capi-openstack.appspot.com/test/flatcar/flatcar-stable-4152.2.3-kube-v1.33.1"
+ OPENSTACK_FLATCAR_IMAGE_NAME: "flatcar-stable"
+ OPENSTACK_FLATCAR_IMAGE_URL: "https://storage.googleapis.com/artifacts.k8s-staging-capi-openstack.appspot.com/test/flatcar/flatcar-stable-4459.2.0-kube-v1.34.2"
# A plain Flatcar from the Flatcar releases server
FLATCAR_IMAGE_NAME: "flatcar_production_openstack_image"
FLATCAR_IMAGE_URL: https://stable.release.flatcar-linux.net/amd64-usr/current/flatcar_production_openstack_image.img
diff --git a/test/e2e/data/kustomize/components/common/kustomization.yaml b/test/e2e/data/kustomize/components/common/kustomization.yaml
index 211dc0e4d..61582df59 100644
--- a/test/e2e/data/kustomize/components/common/kustomization.yaml
+++ b/test/e2e/data/kustomize/components/common/kustomization.yaml
@@ -1,7 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
-resources:
-- images.yaml
+components:
+- ../images
patches:
# Set AZ and enable bastion
- target:
diff --git a/test/e2e/data/kustomize/components/common/images.yaml b/test/e2e/data/kustomize/components/images/images.yaml
similarity index 100%
rename from test/e2e/data/kustomize/components/common/images.yaml
rename to test/e2e/data/kustomize/components/images/images.yaml
diff --git a/test/e2e/data/kustomize/components/images/kustomization.yaml b/test/e2e/data/kustomize/components/images/kustomization.yaml
new file mode 100644
index 000000000..daa515fd8
--- /dev/null
+++ b/test/e2e/data/kustomize/components/images/kustomization.yaml
@@ -0,0 +1,4 @@
+apiVersion: kustomize.config.k8s.io/v1alpha1
+kind: Component
+resources:
+- images.yaml
diff --git a/test/e2e/data/kustomize/components/upgrade-patches/ci-hack-kcp.yaml b/test/e2e/data/kustomize/components/self-hosted/ci-hack-kcp.yaml
similarity index 100%
rename from test/e2e/data/kustomize/components/upgrade-patches/ci-hack-kcp.yaml
rename to test/e2e/data/kustomize/components/self-hosted/ci-hack-kcp.yaml
diff --git a/test/e2e/data/kustomize/components/upgrade-patches/ci-hack-kct.yaml b/test/e2e/data/kustomize/components/self-hosted/ci-hack-kct.yaml
similarity index 100%
rename from test/e2e/data/kustomize/components/upgrade-patches/ci-hack-kct.yaml
rename to test/e2e/data/kustomize/components/self-hosted/ci-hack-kct.yaml
diff --git a/test/e2e/data/kustomize/components/self-hosted/kustomization.yaml b/test/e2e/data/kustomize/components/self-hosted/kustomization.yaml
new file mode 100644
index 000000000..fe2d5cbf3
--- /dev/null
+++ b/test/e2e/data/kustomize/components/self-hosted/kustomization.yaml
@@ -0,0 +1,17 @@
+# Modifications to release templates for self-hosted scenarios.
+# This patch allows the nodes dowload the CAPO image from E2E_IMAGE_URL,
+# and load it. It is useful when testing unpublished images (e.g. a PR).
+# The image is downloaded only if DOWNLOAD_E2E_IMAGE is set to true
+# when rendering the cluster template.
+apiVersion: kustomize.config.k8s.io/v1alpha1
+kind: Component
+
+patches:
+- path: ci-hack-kcp.yaml
+ target:
+ kind: KubeadmControlPlane
+ name: \${CLUSTER_NAME}-control-plane
+- path: ci-hack-kct.yaml
+ target:
+ kind: KubeadmConfigTemplate
+ name: \${CLUSTER_NAME}-md-0
diff --git a/test/e2e/data/kustomize/components/upgrade-patches/kustomization.yaml b/test/e2e/data/kustomize/components/upgrade-patches/kustomization.yaml
deleted file mode 100644
index 678a6a6d7..000000000
--- a/test/e2e/data/kustomize/components/upgrade-patches/kustomization.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-# Modifications to release templates for clusterctl upgrade scenarios
-apiVersion: kustomize.config.k8s.io/v1alpha1
-kind: Component
-
-patches:
-- path: ci-hack-kcp.yaml
- target:
- kind: KubeadmControlPlane
- name: \${CLUSTER_NAME}-control-plane
-- path: ci-hack-kct.yaml
- target:
- kind: KubeadmConfigTemplate
- name: \${CLUSTER_NAME}-md-0
diff --git a/test/e2e/data/kustomize/default/kustomization.yaml b/test/e2e/data/kustomize/default/kustomization.yaml
index f2d25440f..544a9b7a5 100644
--- a/test/e2e/data/kustomize/default/kustomization.yaml
+++ b/test/e2e/data/kustomize/default/kustomization.yaml
@@ -7,4 +7,4 @@ resources:
components:
- ../components/cluster-resource-sets
- ../components/common
-- ../components/upgrade-patches
+- ../components/self-hosted
diff --git a/test/e2e/data/kustomize/multi-az/patch-machine-template-worker.yaml b/test/e2e/data/kustomize/multi-az/patch-machine-template-worker.yaml
index 6d46c42e3..d4c357c51 100644
--- a/test/e2e/data/kustomize/multi-az/patch-machine-template-worker.yaml
+++ b/test/e2e/data/kustomize/multi-az/patch-machine-template-worker.yaml
@@ -16,8 +16,8 @@
volume:
type: ${OPENSTACK_VOLUME_TYPE_ALT}
availabilityZone:
- name: ${OPENSTACK_FAILURE_DOMAIN}
+ name: ${OPENSTACK_FAILURE_DOMAIN}
- name: etcd
sizeGiB: 1
storage:
- type: Local
\ No newline at end of file
+ type: Local
diff --git a/test/e2e/data/kustomize/topology/cluster.yaml b/test/e2e/data/kustomize/topology/cluster.yaml
new file mode 100644
index 000000000..477715f26
--- /dev/null
+++ b/test/e2e/data/kustomize/topology/cluster.yaml
@@ -0,0 +1,40 @@
+# Rather than editing the cluster-template, which include already variables,
+# we simply create a new cluster-template with the correct variables for e2e.
+apiVersion: cluster.x-k8s.io/v1beta2
+kind: Cluster
+metadata:
+ name: ${CLUSTER_NAME}
+spec:
+ topology:
+ classRef:
+ name: dev-test
+ version: ${KUBERNETES_VERSION}
+ controlPlane:
+ replicas: ${CONTROL_PLANE_MACHINE_COUNT}
+ workers:
+ machineDeployments:
+ - class: default-worker
+ name: md-0
+ replicas: ${WORKER_MACHINE_COUNT}
+ variables:
+ - name: identityRef
+ value:
+ name: ${CLUSTER_NAME}-cloud-config
+ cloudName: ${OPENSTACK_CLOUD}
+ - name: imageRef
+ value: node-image
+ - name: addImageVersion
+ value: false
+ - name: injectIgnitionSysext
+ value: true
+ - name: allowedCIDRs
+ value: ${OPENSTACK_API_SERVER_ALLOWED_CIDRS:=[]}
+ - name: bastion
+ value:
+ enabled: ${OPENSTACK_BASTION_ENABLED:=false}
+ spec:
+ flavor: ${OPENSTACK_BASTION_FLAVOR:=m1.small}
+ image:
+ imageRef:
+ name: bastion-image
+ sshKeyName: ${OPENSTACK_SSH_KEY_NAME:=""}
diff --git a/test/e2e/data/kustomize/topology/kustomization.yaml b/test/e2e/data/kustomize/topology/kustomization.yaml
new file mode 100644
index 000000000..b2ad517cd
--- /dev/null
+++ b/test/e2e/data/kustomize/topology/kustomization.yaml
@@ -0,0 +1,24 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+
+resources:
+- cluster.yaml
+- secret.yaml
+
+components:
+- ../components/images
+- ../components/cluster-resource-sets
+
+patches:
+- target:
+ group: openstack.k-orc.cloud
+ version: v1alpha1
+ kind: Image
+ name: node-image
+ patch: |-
+ - op: replace
+ path: /spec/resource/name
+ value: ${FLATCAR_IMAGE_NAME}
+ - op: replace
+ path: /spec/resource/content/download/url
+ value: ${FLATCAR_IMAGE_URL}
diff --git a/test/e2e/data/kustomize/topology/secret.yaml b/test/e2e/data/kustomize/topology/secret.yaml
new file mode 100644
index 000000000..2d6641ada
--- /dev/null
+++ b/test/e2e/data/kustomize/topology/secret.yaml
@@ -0,0 +1,9 @@
+apiVersion: v1
+data:
+ cacert: ${OPENSTACK_CLOUD_CACERT_B64}
+ clouds.yaml: ${OPENSTACK_CLOUD_YAML_B64}
+kind: Secret
+metadata:
+ labels:
+ clusterctl.cluster.x-k8s.io/move: "true"
+ name: ${CLUSTER_NAME}-cloud-config
diff --git a/test/e2e/shared/cluster.go b/test/e2e/shared/cluster.go
index 6373eefd5..388920ffb 100644
--- a/test/e2e/shared/cluster.go
+++ b/test/e2e/shared/cluster.go
@@ -1,5 +1,4 @@
//go:build e2e
-// +build e2e
/*
Copyright 2021 The Kubernetes Authors.
diff --git a/test/e2e/shared/common.go b/test/e2e/shared/common.go
index 791e440fb..13e8f5bbb 100644
--- a/test/e2e/shared/common.go
+++ b/test/e2e/shared/common.go
@@ -1,5 +1,4 @@
//go:build e2e
-// +build e2e
/*
Copyright 2021 The Kubernetes Authors.
@@ -169,7 +168,8 @@ func getOpenStackClusterFromMachine(ctx context.Context, client client.Client, m
}
key = types.NamespacedName{
- Name: cluster.Spec.InfrastructureRef.Name,
+ Name: cluster.Spec.InfrastructureRef.Name,
+ Namespace: cluster.Namespace,
}
openStackCluster := &infrav1.OpenStackCluster{}
err = client.Get(ctx, key, openStackCluster)
diff --git a/test/e2e/shared/context.go b/test/e2e/shared/context.go
index 4711def1a..d626b6447 100644
--- a/test/e2e/shared/context.go
+++ b/test/e2e/shared/context.go
@@ -1,5 +1,4 @@
//go:build e2e
-// +build e2e
/*
Copyright 2021 The Kubernetes Authors.
diff --git a/test/e2e/shared/defaults.go b/test/e2e/shared/defaults.go
index e14af4892..9da8c4ad4 100644
--- a/test/e2e/shared/defaults.go
+++ b/test/e2e/shared/defaults.go
@@ -1,5 +1,4 @@
//go:build e2e
-// +build e2e
/*
Copyright 2021 The Kubernetes Authors.
@@ -37,6 +36,7 @@ import (
const (
DefaultSSHKeyPairName = "cluster-api-provider-openstack-sigs-k8s-io"
KubeContext = "KUBE_CONTEXT"
+ KubernetesKindVersion = "KUBERNETES_KIND_VERSION"
KubernetesVersion = "KUBERNETES_VERSION"
CCMPath = "CCM"
CCMResources = "CCM_RESOURCES"
@@ -52,6 +52,7 @@ const (
OpenStackNodeMachineFlavor = "OPENSTACK_NODE_MACHINE_FLAVOR"
SSHUserMachine = "SSH_USER_MACHINE"
FlavorDefault = ""
+ FlavorTopology = "topology"
FlavorNoBastion = "no-bastion"
FlavorWithoutLB = "without-lb"
FlavorMultiNetwork = "multi-network"
diff --git a/test/e2e/shared/exec.go b/test/e2e/shared/exec.go
index e26f9e3fd..371e1ff5e 100644
--- a/test/e2e/shared/exec.go
+++ b/test/e2e/shared/exec.go
@@ -1,5 +1,4 @@
//go:build e2e
-// +build e2e
/*
Copyright 2021 The Kubernetes Authors.
diff --git a/test/e2e/shared/exec_test.go b/test/e2e/shared/exec_test.go
index 2e71b57b0..2eb77319a 100644
--- a/test/e2e/shared/exec_test.go
+++ b/test/e2e/shared/exec_test.go
@@ -1,5 +1,4 @@
//go:build e2e
-// +build e2e
/*
Copyright 2021 The Kubernetes Authors.
diff --git a/test/e2e/shared/images.go b/test/e2e/shared/images.go
index 110412384..403be1ce9 100644
--- a/test/e2e/shared/images.go
+++ b/test/e2e/shared/images.go
@@ -1,5 +1,4 @@
//go:build e2e
-// +build e2e
/*
Copyright 2024 The Kubernetes Authors.
diff --git a/test/e2e/shared/openstack.go b/test/e2e/shared/openstack.go
index 7c7128b76..c79add269 100644
--- a/test/e2e/shared/openstack.go
+++ b/test/e2e/shared/openstack.go
@@ -1,5 +1,4 @@
//go:build e2e
-// +build e2e
/*
Copyright 2021 The Kubernetes Authors.
diff --git a/test/e2e/shared/openstack_test.go b/test/e2e/shared/openstack_test.go
index 1f1703aa0..340e10f31 100644
--- a/test/e2e/shared/openstack_test.go
+++ b/test/e2e/shared/openstack_test.go
@@ -1,5 +1,4 @@
//go:build e2e
-// +build e2e
/*
Copyright 2021 The Kubernetes Authors.
diff --git a/test/e2e/shared/suite.go b/test/e2e/shared/suite.go
index 5d48d9501..54a0d2f48 100644
--- a/test/e2e/shared/suite.go
+++ b/test/e2e/shared/suite.go
@@ -1,5 +1,4 @@
//go:build e2e
-// +build e2e
/*
Copyright 2021 The Kubernetes Authors.
diff --git a/test/e2e/suites/conformance/conformance_suite_test.go b/test/e2e/suites/conformance/conformance_suite_test.go
index 3971c55fa..b1ddac676 100644
--- a/test/e2e/suites/conformance/conformance_suite_test.go
+++ b/test/e2e/suites/conformance/conformance_suite_test.go
@@ -1,5 +1,4 @@
//go:build e2e
-// +build e2e
/*
Copyright 2021 The Kubernetes Authors.
diff --git a/test/e2e/suites/conformance/conformance_test.go b/test/e2e/suites/conformance/conformance_test.go
index 6f218349b..f0cf00c85 100644
--- a/test/e2e/suites/conformance/conformance_test.go
+++ b/test/e2e/suites/conformance/conformance_test.go
@@ -1,5 +1,4 @@
//go:build e2e
-// +build e2e
/*
Copyright 2021 The Kubernetes Authors.
diff --git a/test/e2e/suites/e2e/clusterctl_upgrade_test.go b/test/e2e/suites/e2e/clusterctl_upgrade_test.go
index 1af541d48..2b0947e48 100644
--- a/test/e2e/suites/e2e/clusterctl_upgrade_test.go
+++ b/test/e2e/suites/e2e/clusterctl_upgrade_test.go
@@ -1,5 +1,4 @@
//go:build e2e
-// +build e2e
/*
Copyright 2021 The Kubernetes Authors.
@@ -40,7 +39,6 @@ var (
var _ = Describe("When testing clusterctl upgrades for CAPO (v0.11=>current) and ORC (v1.0.2=>current) [clusterctl-upgrade]", func() {
BeforeEach(func(ctx context.Context) {
- setDownloadE2EImageEnvVar()
// Note: This gives the version without the 'v' prefix, so we need to add it below.
capoRelease011, err = clusterctl.ResolveRelease(ctx, "go://github.com/kubernetes-sigs/cluster-api-provider-openstack@v0.11")
Expect(err).ToNot(HaveOccurred(), "failed to get stable release of CAPO")
@@ -66,15 +64,15 @@ var _ = Describe("When testing clusterctl upgrades for CAPO (v0.11=>current) and
InitWithControlPlaneProviders: []string{"kubeadm:" + capiRelease110},
MgmtFlavor: shared.FlavorDefault,
WorkloadFlavor: shared.FlavorCapiV1Beta1,
- InitWithKubernetesVersion: e2eCtx.E2EConfig.MustGetVariable(shared.KubernetesVersion),
+ InitWithKubernetesVersion: e2eCtx.E2EConfig.MustGetVariable(shared.KubernetesKindVersion),
InitWithRuntimeExtensionProviders: []string{"openstack-resource-controller:v1.0.2"},
+ UseKindForManagementCluster: true,
}
})
})
var _ = Describe("When testing clusterctl upgrades for CAPO (v0.12=>current) and ORC (v1.0.2=>current)[clusterctl-upgrade]", func() {
BeforeEach(func(ctx context.Context) {
- setDownloadE2EImageEnvVar()
// Note: This gives the version without the 'v' prefix, so we need to add it below.
capoRelease012, err = clusterctl.ResolveRelease(ctx, "go://github.com/kubernetes-sigs/cluster-api-provider-openstack@v0.12")
Expect(err).ToNot(HaveOccurred(), "failed to get stable release of CAPO")
@@ -100,15 +98,15 @@ var _ = Describe("When testing clusterctl upgrades for CAPO (v0.12=>current) and
InitWithControlPlaneProviders: []string{"kubeadm:" + capiRelease110},
MgmtFlavor: shared.FlavorDefault,
WorkloadFlavor: shared.FlavorCapiV1Beta1,
- InitWithKubernetesVersion: e2eCtx.E2EConfig.MustGetVariable(shared.KubernetesVersion),
+ InitWithKubernetesVersion: e2eCtx.E2EConfig.MustGetVariable(shared.KubernetesKindVersion),
InitWithRuntimeExtensionProviders: []string{"openstack-resource-controller:v1.0.2"},
+ UseKindForManagementCluster: true,
}
})
})
var _ = Describe("When testing clusterctl upgrades for CAPO (v0.13=>current) and ORC (v1.0.2=>current)[clusterctl-upgrade]", func() {
BeforeEach(func(ctx context.Context) {
- setDownloadE2EImageEnvVar()
// Note: This gives the version without the 'v' prefix, so we need to add it below.
capoRelease013, err = clusterctl.ResolveRelease(ctx, "go://github.com/kubernetes-sigs/cluster-api-provider-openstack@v0.13")
Expect(err).ToNot(HaveOccurred(), "failed to get stable release of CAPO")
@@ -134,8 +132,9 @@ var _ = Describe("When testing clusterctl upgrades for CAPO (v0.13=>current) and
InitWithControlPlaneProviders: []string{"kubeadm:" + capiRelease111},
MgmtFlavor: shared.FlavorDefault,
WorkloadFlavor: shared.FlavorDefault,
- InitWithKubernetesVersion: e2eCtx.E2EConfig.MustGetVariable(shared.KubernetesVersion),
+ InitWithKubernetesVersion: e2eCtx.E2EConfig.MustGetVariable(shared.KubernetesKindVersion),
InitWithRuntimeExtensionProviders: []string{"openstack-resource-controller:v1.0.2"},
+ UseKindForManagementCluster: true,
}
})
})
diff --git a/test/e2e/suites/e2e/e2e_suite_test.go b/test/e2e/suites/e2e/e2e_suite_test.go
index a4fb17dad..ebb1396db 100644
--- a/test/e2e/suites/e2e/e2e_suite_test.go
+++ b/test/e2e/suites/e2e/e2e_suite_test.go
@@ -1,5 +1,4 @@
//go:build e2e
-// +build e2e
/*
Copyright 2021 The Kubernetes Authors.
diff --git a/test/e2e/suites/e2e/e2e_test.go b/test/e2e/suites/e2e/e2e_test.go
index 3fbdd4862..8e718a059 100644
--- a/test/e2e/suites/e2e/e2e_test.go
+++ b/test/e2e/suites/e2e/e2e_test.go
@@ -1,5 +1,4 @@
//go:build e2e
-// +build e2e
/*
Copyright 2021 The Kubernetes Authors.
diff --git a/test/e2e/suites/e2e/quick_start_test.go b/test/e2e/suites/e2e/quick_start_test.go
new file mode 100644
index 000000000..f8a9e7244
--- /dev/null
+++ b/test/e2e/suites/e2e/quick_start_test.go
@@ -0,0 +1,42 @@
+//go:build e2e
+
+/*
+Copyright 2024 The Kubernetes Authors.
+
+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 e2e
+
+import (
+ "context"
+
+ . "github.com/onsi/ginkgo/v2"
+ "k8s.io/utils/ptr"
+ capi_e2e "sigs.k8s.io/cluster-api/test/e2e"
+
+ "sigs.k8s.io/cluster-api-provider-openstack/test/e2e/shared"
+)
+
+var _ = Describe("When following the Cluster API quick-start with ClusterClass [PR-Blocking] [ClusterClass]", func() {
+ capi_e2e.QuickStartSpec(context.TODO(), func() capi_e2e.QuickStartSpecInput {
+ return capi_e2e.QuickStartSpecInput{
+ E2EConfig: e2eCtx.E2EConfig,
+ ClusterctlConfigPath: e2eCtx.Environment.ClusterctlConfigPath,
+ BootstrapClusterProxy: e2eCtx.Environment.BootstrapClusterProxy,
+ ArtifactFolder: e2eCtx.Settings.ArtifactFolder,
+ SkipCleanup: false,
+ Flavor: ptr.To(shared.FlavorTopology),
+ }
+ })
+})
diff --git a/test/e2e/suites/e2e/remediations_test.go b/test/e2e/suites/e2e/remediations_test.go
index d8df1765d..ed2226c2f 100644
--- a/test/e2e/suites/e2e/remediations_test.go
+++ b/test/e2e/suites/e2e/remediations_test.go
@@ -1,5 +1,4 @@
//go:build e2e
-// +build e2e
/*
Copyright 2021 The Kubernetes Authors.
diff --git a/test/e2e/suites/e2e/self_hosted_test.go b/test/e2e/suites/e2e/self_hosted_test.go
index 3ad151101..400744549 100644
--- a/test/e2e/suites/e2e/self_hosted_test.go
+++ b/test/e2e/suites/e2e/self_hosted_test.go
@@ -1,5 +1,4 @@
//go:build e2e
-// +build e2e
/*
Copyright 2022 The Kubernetes Authors.
@@ -30,6 +29,8 @@ import (
var _ = Describe("When testing Cluster API provider Openstack working on [self-hosted] clusters", func() {
BeforeEach(func() {
+ // The self-hosted cluster does not have the CAPO image loaded like the kind cluster does,
+ // so we download it from E2E_IMAGE_URL.
setDownloadE2EImageEnvVar()
})