diff --git a/.dockerignore b/.dockerignore index 9c5e439d..723093e1 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,25 @@ -# SPDX-FileCopyrightText: Copyright 2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors -# +# SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company # SPDX-License-Identifier: Apache-2.0 -# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file -# Ignore build and test binaries. -bin/ + +.DS_Store +/*.env* +/.dockerignore +# TODO: uncomment when applications no longer use git to get version information +#.git/ +/.github/ +/.gitignore +/.golangci.yaml +/.goreleaser.yml +/.vscode/ +/CONTRIBUTING.md +/Dockerfile +/LICENSE* +/Makefile.maker.yaml +/README.md +/build/ +/docs/ +/go.work +/go.work.sum +/report.html +/shell.nix +/testing/ diff --git a/.editorconfig b/.editorconfig index baf3191c..46ace684 100644 --- a/.editorconfig +++ b/.editorconfig @@ -23,4 +23,4 @@ end_of_line = unset indent_size = unset indent_style = unset insert_final_newline = unset -trim_trailing_whitespace = unset \ No newline at end of file +trim_trailing_whitespace = unset diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..a40910f6 --- /dev/null +++ b/.envrc @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: 2019–2020 Target +# SPDX-FileCopyrightText: 2021 The Nix Community +# SPDX-License-Identifier: Apache-2.0 +if type -P lorri &>/dev/null; then + eval "$(lorri direnv)" +elif type -P nix &>/dev/null; then + use nix +else + echo "Found no nix binary. Skipping activating nix-shell..." +fi diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 1c88ea14..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,23 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file - -version: 2 -updates: - # Maintain dependencies for GitHub Actions - - package-ecosystem: "github-actions" - # Workflow files stored in the default location of `.github/workflows`. (You don't need to specify `/.github/workflows` for `directory`. You can use `directory: "/"`.) - directory: "/" - schedule: - interval: "weekly" - - - package-ecosystem: "docker" - directory: "/" - schedule: - interval: "weekly" - - - package-ecosystem: "gomod" - directory: "/" - schedule: - interval: "weekly" diff --git a/.github/renovate.json b/.github/renovate.json index d391cdf5..8fe8c0ce 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,23 +1,76 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ - "config:recommended" + "config:recommended", + "default:pinDigestsDisabled", + "mergeConfidence:all-badges", + "docker:disable" ], "assignees": [ - "notandy" + "notandy", + "fwiesel", + "mchristianl", + "toanju" + ], + "commitMessageAction": "Renovate: Update", + "constraints": { + "go": "1.25" + }, + "dependencyDashboardOSVVulnerabilitySummary": "all", + "osvVulnerabilityAlerts": true, + "postUpdateOptions": [ + "gomodTidy", + "gomodUpdateImportPaths" ], "packageRules": [ { + "matchPackageNames": [ + "/.*/" + ], + "matchUpdateTypes": [ + "minor", + "patch" + ], + "groupName": "External dependencies" + }, + { + "matchPackageNames": [ + "/^github\\.com\\/sapcc\\/.*/" + ], + "automerge": true, + "groupName": "github.com/sapcc" + }, + { + "matchPackageNames": [ + "go", + "golang", + "actions/go-versions" + ], + "groupName": "golang", + "separateMinorPatch": true + }, + { + "matchPackageNames": [ + "go", + "golang", + "actions/go-versions" + ], "matchUpdateTypes": [ "minor", - "patch", - "pin", - "digest" + "major" ], - "automerge": true + "dependencyDashboardApproval": true + }, + { + "matchPackageNames": [ + "/^k8s.io\\//" + ], + "allowedVersions": "0.28.x" } ], - "postUpdateOptions": [ - "gomodTidy" - ] + "prHourlyLimit": 0, + "schedule": [ + "before 8am on Friday" + ], + "semanticCommits": "disabled" } diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index e9674c90..40c58b65 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -1,16 +1,23 @@ +################################################################################ +# This file is AUTOGENERATED with # +# Edit Makefile.maker.yaml instead. # +################################################################################ + +# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company +# SPDX-License-Identifier: Apache-2.0 + name: Checks -on: +"on": push: - tags: - - v* branches: - main pull_request: - + branches: + - '*' + workflow_dispatch: {} permissions: checks: write contents: read - jobs: checks: name: Checks @@ -21,28 +28,31 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version-file: 'go.mod' - - run: go mod edit -json | jq -r .Go | echo "GO_VERSION_FROM_PROJECT=$(cut -d' ' -f2)" >>${GITHUB_ENV} - - name: Dependency Review - uses: actions/dependency-review-action@v4 - with: - base-ref: ${{ github.event.pull_request.base.sha || 'main' }} - deny-licenses: AGPL-1.0, AGPL-3.0, GPL-1.0, GPL-2.0, GPL-3.0, LGPL-2.0, LGPL-2.1, LGPL-3.0, BUSL-1.1 - fail-on-severity: moderate - head-ref: ${{ github.event.pull_request.head.sha || github.ref }} - - name: Run govulncheck - uses: golang/govulncheck-action@v1 + check-latest: true + go-version: 1.25.3 + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v8 with: - go-version-input: "${{ env.GO_VERSION_FROM_PROJECT }}" + version: latest + - name: Delete pre-installed shellcheck + run: sudo rm -f $(which shellcheck) + - name: Run shellcheck + run: make run-shellcheck + - name: Dependency Licenses Review + run: make check-dependency-licenses - name: Check for spelling errors uses: reviewdog/action-misspell@v1 with: + exclude: ./vendor/* fail_on_error: true github_token: ${{ secrets.GITHUB_TOKEN }} ignore: importas reporter: github-check - name: Check if source code files have license header - run: | - shopt -s globstar - go install github.com/google/addlicense@latest - addlicense --check -- **/*.go + run: make check-addlicense + - name: REUSE Compliance Check + uses: fsfe/reuse-action@v6 + - name: Install govulncheck + run: go install golang.org/x/vuln/cmd/govulncheck@latest + - name: Run govulncheck + run: govulncheck -format text ./... diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..083c2a05 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,75 @@ +################################################################################ +# This file is AUTOGENERATED with # +# Edit Makefile.maker.yaml instead. # +################################################################################ + +# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company +# SPDX-License-Identifier: Apache-2.0 + +name: CI +"on": + push: + branches: + - main + paths-ignore: + - '**.md' + pull_request: + branches: + - '*' + paths-ignore: + - '**.md' + workflow_dispatch: {} +permissions: + contents: read +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v5 + - name: Set up Go + uses: actions/setup-go@v6 + with: + check-latest: true + go-version: 1.25.3 + - name: Build all binaries + run: make build-all + code_coverage: + name: Code coverage report + if: github.event_name == 'pull_request' + needs: + - test + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v5 + - name: Post coverage report + uses: fgrosse/go-coverage-report@v1.2.0 + with: + coverage-artifact-name: code-coverage + coverage-file-name: cover.out + permissions: + actions: read + contents: read + pull-requests: write + test: + name: Test + needs: + - build + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v5 + - name: Set up Go + uses: actions/setup-go@v6 + with: + check-latest: true + go-version: 1.25.3 + - name: Run tests and generate coverage report + run: make build/cover.out + - name: Archive code coverage results + uses: actions/upload-artifact@v4 + with: + name: code-coverage + path: build/cover.out diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml new file mode 100644 index 00000000..bb1e370c --- /dev/null +++ b/.github/workflows/codeql.yaml @@ -0,0 +1,44 @@ +################################################################################ +# This file is AUTOGENERATED with # +# Edit Makefile.maker.yaml instead. # +################################################################################ + +# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company +# SPDX-License-Identifier: Apache-2.0 + +name: CodeQL +"on": + push: + branches: + - main + pull_request: + branches: + - main + schedule: + - cron: '00 07 * * 1' + workflow_dispatch: {} +permissions: + actions: read + contents: read + security-events: write +jobs: + analyze: + name: CodeQL + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v5 + - name: Set up Go + uses: actions/setup-go@v6 + with: + check-latest: true + go-version: 1.25.3 + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: go + queries: security-extended + - name: Autobuild + uses: github/codeql-action/autobuild@v4 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml deleted file mode 100644 index fc17874f..00000000 --- a/.github/workflows/go-test.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: go-test - -on: - push: - tags: - - v* - branches: - - main - pull_request: - -jobs: - run-unit-tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - name: Set up Go - uses: actions/setup-go@v6 - with: - go-version-file: 'go.mod' - - name: Run test - run: make test diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml deleted file mode 100644 index 66498d3d..00000000 --- a/.github/workflows/golangci-lint.yml +++ /dev/null @@ -1,49 +0,0 @@ -# SPDX-FileCopyrightText: Copyright 2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors -# -# SPDX-License-Identifier: Apache-2.0 -name: golangci-lint -on: - push: - tags: - - v* - branches: - - main - pull_request: -permissions: - contents: read - # Optional: allow read access to pull request. Use with `only-new-issues` option. - # pull-requests: read -jobs: - golangci: - name: lint - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - uses: actions/setup-go@v6 - with: - go-version-file: 'go.mod' - - name: golangci-lint - uses: golangci/golangci-lint-action@v8 - with: - # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version - version: latest - - - # Optional: working directory, useful for monorepos - # working-directory: somedir - - # Optional: golangci-lint command line arguments. - args: --timeout=10m - - # Optional: show only new issues if it's a pull request. The default value is `false`. - # only-new-issues: true - - # Optional: if set to true then the all caching functionality will be complete disabled, - # takes precedence over all other caching options. - skip-cache: true - - # Optional: if set to true then the action don't cache or restore ~/go/pkg. - # skip-pkg-cache: true - - # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. - # skip-build-cache: true diff --git a/.github/workflows/reuse.yaml b/.github/workflows/reuse.yaml deleted file mode 100644 index 9f46866f..00000000 --- a/.github/workflows/reuse.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# SPDX-FileCopyrightText: Copyright 2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors -# -# SPDX-License-Identifier: Apache-2.0 - -name: REUSE Compliance Check - -on: [push, pull_request] - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - name: REUSE Compliance Check - uses: fsfe/reuse-action@v6.0.0 diff --git a/.gitignore b/.gitignore index ba22c5e1..e88d3729 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ *.dylib bin/* Dockerfile.cross +build/* # Test binary, built with `go test -c` *.test diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 00000000..48b7e649 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,195 @@ +################################################################################ +# This file is AUTOGENERATED with # +# Edit Makefile.maker.yaml instead. # +################################################################################ + +# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company +# SPDX-License-Identifier: Apache-2.0 + +version: "2" +run: + modules-download-mode: readonly + timeout: 3m0s # none by default in v2 + +formatters: + enable: + - gofmt + - goimports + settings: + goimports: + # Put local imports after 3rd-party packages + local-prefixes: + - github.com/cobaltcore-dev/openstack-hypervisor-operator + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ + +issues: + # '0' disables the following options + max-issues-per-linter: 0 + max-same-issues: 0 + +linters: + # Disable all pre-enabled linters and enable them explicitly so that a newer version does not introduce new linters unexpectedly + default: none + enable: + - bodyclose + - containedctx + - copyloopvar + - dupword + - durationcheck + - errcheck + - errname + - errorlint + - exptostd + - forbidigo + - ginkgolinter + - gocheckcompilerdirectives + - goconst + - gocritic + - gomoddirectives + - gosec + - govet + - ineffassign + - intrange + - iotamixing + - misspell + - nilerr + - nolintlint + - nosprintfhostport + - perfsprint + - predeclared + - rowserrcheck + - sqlclosecheck + - staticcheck + - unconvert + - unparam + - unused + - usestdlibvars + - usetesting + - whitespace + settings: + dupword: + # Do not choke on SQL statements like `INSERT INTO things (foo, bar, baz) VALUES (TRUE, TRUE, TRUE)`. + ignore: [ "TRUE", "FALSE", "NULL" ] + errcheck: + check-type-assertions: false + # Report about assignment of errors to blank identifier. + check-blank: true + # Do not report about not checking of errors in type assertions. + # This is not as dangerous as skipping error values because an unchecked type assertion just immediately panics. + # We disable this because it makes a ton of useless noise esp. in test code. + forbidigo: + analyze-types: true # required for pkg: + forbid: + # ioutil package has been deprecated: https://github.com/golang/go/issues/42026 + - pattern: ^ioutil\..*$ + # Using http.DefaultServeMux is discouraged because it's a global variable that some packages silently and magically add handlers to (esp. net/http/pprof). + # Applications wishing to use http.ServeMux should obtain local instances through http.NewServeMux() instead of using the global default instance. + - pattern: ^http\.DefaultServeMux$ + - pattern: ^http\.Handle(?:Func)?$ + - pkg: ^gopkg\.in/square/go-jose\.v2$ + msg: gopk.in/square/go-jose is archived and has CVEs. Replace it with gopkg.in/go-jose/go-jose.v2 + - pkg: ^github.com/coreos/go-oidc$ + msg: github.com/coreos/go-oidc depends on gopkg.in/square/go-jose which has CVEs. Replace it with github.com/coreos/go-oidc/v3 + - pkg: ^github.com/howeyc/gopass$ + msg: github.com/howeyc/gopass is archived, use golang.org/x/term instead + goconst: + min-occurrences: 5 + gocritic: + enabled-checks: + - boolExprSimplify + - builtinShadow + - emptyStringTest + - evalOrder + - httpNoBody + - importShadow + - initClause + - methodExprCall + - paramTypeCombine + - preferFilepathJoin + - ptrToRefParam + - redundantSprint + - returnAfterHttpError + - stringConcatSimplify + - timeExprSimplify + - truncateCmp + - typeAssertChain + - typeUnparen + - unnamedResult + - unnecessaryBlock + - unnecessaryDefer + - weakCond + - yodaStyleExpr + gomoddirectives: + replace-allow-list: + # for go-pmtud + - github.com/mdlayher/arp + # for github.com/sapcc/vpa_butler + - k8s.io/client-go + toolchain-forbidden: true + go-version-pattern: 1\.\d+(\.0)?$ + gosec: + excludes: + # gosec wants us to set a short ReadHeaderTimeout to avoid Slowloris attacks, but doing so would expose us to Keep-Alive race conditions (see https://iximiuz.com/en/posts/reverse-proxy-http-keep-alive-and-502s/ + - G112 + # created file permissions are restricted by umask if necessary + - G306 + govet: + disable: + - fieldalignment + enable-all: true + nolintlint: + require-specific: true + staticcheck: + dot-import-whitelist: + - github.com/majewsky/gg/option + - github.com/onsi/ginkgo/v2 + - github.com/onsi/gomega + usestdlibvars: + http-method: true + http-status-code: true + time-weekday: true + time-month: true + time-layout: true + crypto-hash: true + default-rpc-path: true + sql-isolation-level: true + tls-signature-scheme: true + constant-kind: true + usetesting: + os-temp-dir: true + whitespace: + # Enforce newlines (or comments) after multi-line function signatures. + multi-func: true + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - linters: + - bodyclose + path: _test\.go + # It is idiomatic Go to reuse the name 'err' with ':=' for subsequent errors. + # Ref: https://go.dev/doc/effective_go#redeclaration + - path: (.+)\.go$ + text: declaration of "err" shadows declaration at + - linters: + - goconst + path: (.+)_test\.go + paths: + - third_party$ + - builtin$ + - examples$ + +output: + formats: + text: + # Do not print lines of code with issue. + print-issued-lines: false diff --git a/.golangci.yml b/.golangci.yml deleted file mode 100644 index 859de35a..00000000 --- a/.golangci.yml +++ /dev/null @@ -1,53 +0,0 @@ -# SPDX-FileCopyrightText: Copyright 2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors -# -# SPDX-License-Identifier: Apache-2.0 -version: "2" -run: - allow-parallel-runners: true -linters: - default: none - enable: - - dupl - - errcheck - - ginkgolinter - - goconst - - gocyclo - - govet - - ineffassign - - lll - - misspell - - nakedret - - prealloc - - revive - - staticcheck - - unconvert - - unparam - - unused - settings: - revive: - rules: - - name: comment-spacings - exclusions: - generated: lax - rules: - - linters: - - lll - path: api/* - - linters: - - dupl - - lll - path: internal/* - paths: - - third_party$ - - builtin$ - - examples$ -formatters: - enable: - - gofmt - - goimports - exclusions: - generated: lax - paths: - - third_party$ - - builtin$ - - examples$ diff --git a/.license-scan-overrides.jsonl b/.license-scan-overrides.jsonl new file mode 100644 index 00000000..0a8feb2e --- /dev/null +++ b/.license-scan-overrides.jsonl @@ -0,0 +1,11 @@ +{"name": "github.com/chzyer/logex", "licenceType": "MIT"} +{"name": "github.com/hashicorp/vault/api/auth/approle", "licenceType": "MPL-2.0"} +{"name": "github.com/jpillora/longestcommon", "licenceType": "MIT"} +{"name": "github.com/logrusorgru/aurora", "licenceType": "Unlicense"} +{"name": "github.com/mattn/go-localereader", "licenceType": "MIT"} +{"name": "github.com/miekg/dns", "licenceType": "BSD-3-Clause"} +{"name": "github.com/pashagolub/pgxmock/v4", "licenceType": "BSD-3-Clause"} +{"name": "github.com/spdx/tools-golang", "licenceTextOverrideFile": "vendor/github.com/spdx/tools-golang/LICENSE.code"} +{"name": "github.com/xeipuuv/gojsonpointer", "licenceType": "Apache-2.0"} +{"name": "github.com/xeipuuv/gojsonreference", "licenceType": "Apache-2.0"} +{"name": "github.com/xeipuuv/gojsonschema", "licenceType": "Apache-2.0"} diff --git a/.license-scan-rules.json b/.license-scan-rules.json new file mode 100644 index 00000000..909cc0fa --- /dev/null +++ b/.license-scan-rules.json @@ -0,0 +1,14 @@ +{ + "allowlist": [ + "Apache-2.0", + "BSD-2-Clause", + "BSD-2-Clause-FreeBSD", + "BSD-3-Clause", + "EPL-2.0", + "ISC", + "MIT", + "MPL-2.0", + "Unlicense", + "Zlib" + ] +} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0f72d505..6987eca2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,10 +9,6 @@ repos: - id: end-of-file-fixer exclude: ^charts/.* - id: check-added-large-files - - repo: https://github.com/golangci/golangci-lint - rev: v2.1.6 - hooks: - - id: golangci-lint - repo: https://github.com/dnephin/pre-commit-golang rev: v0.5.1 hooks: @@ -20,28 +16,24 @@ repos: - id: go-imports exclude: .*/(.+_mock\.go|.+deepcopy\.go)$ - id: go-mod-tidy - - repo: https://github.com/fsfe/reuse-tool - rev: v5.0.2 - hooks: - - id: reuse - repo: https://github.com/gruntwork-io/pre-commit - rev: v0.1.29 + rev: v0.1.30 hooks: - id: helmlint - repo: local hooks: - id: go-unit name: go unit tests - entry: make test + entry: sh -c "gmake check || make check" language: system pass_filenames: false - id: helmify name: helmify - entry: make helm + entry: sh -c "gmake helmify || make helmify" language: system pass_filenames: false - id: go-build name: go build - entry: make build + entry: sh -c "gmake build-all || make build-all" language: system pass_filenames: false diff --git a/Dockerfile b/Dockerfile index 0971f33b..167240f6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,37 +1,40 @@ -# SPDX-FileCopyrightText: Copyright 2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors -# +# SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company # SPDX-License-Identifier: Apache-2.0 -# Build the manager binary -FROM golang:1.25 AS builder -ARG TARGETOS -ARG TARGETARCH - -WORKDIR /workspace -# Copy the Go Modules manifests -COPY go.mod go.mod -COPY go.sum go.sum -# cache deps before building and copying source so that we don't need to re-download as much -# and so that source changes don't invalidate our downloaded layer -RUN go mod download - -# Copy the go source -COPY cmd/main.go cmd/main.go -COPY api/ api/ -COPY internal/ internal/ - -# Build -# the GOARCH has not a default value to allow the binary be built according to the host where the command -# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO -# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, -# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. -RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go - -# Use distroless as minimal base image to package the manager binary -# Refer to https://github.com/GoogleContainerTools/distroless for more details -FROM gcr.io/distroless/static:nonroot -LABEL source_repository="https://github.com/cobaltcore-dev/openstack-hypervisor-operator" -WORKDIR / -COPY --from=builder /workspace/manager . -USER 65532:65532 - -ENTRYPOINT ["/manager"] + +FROM golang:1.25.3-alpine3.22 AS builder + +RUN apk add --no-cache --no-progress ca-certificates gcc git make musl-dev + +COPY . /src +ARG BININFO_BUILD_DATE BININFO_COMMIT_HASH BININFO_VERSION # provided to 'make install' +RUN make -C /src install PREFIX=/pkg GOTOOLCHAIN=local + +################################################################################ + +FROM alpine:3.22 + +RUN addgroup -g 4200 appgroup \ + && adduser -h /home/appuser -s /sbin/nologin -G appgroup -D -u 4200 appuser + +# upgrade all installed packages to fix potential CVEs in advance +# also remove apk package manager to hopefully remove dependency on OpenSSL 🤞 +RUN apk upgrade --no-cache --no-progress \ + && apk del --no-cache --no-progress apk-tools alpine-keys alpine-release libc-utils + +COPY --from=builder /etc/ssl/certs/ /etc/ssl/certs/ +COPY --from=builder /etc/ssl/cert.pem /etc/ssl/cert.pem +COPY --from=builder /pkg/ /usr/ +# make sure all binaries can be executed +RUN set -x \ + && manager --version 2>/dev/null + +ARG BININFO_BUILD_DATE BININFO_COMMIT_HASH BININFO_VERSION +LABEL source_repository="https://github.com/cobaltcore-dev/openstack-hypervisor-operator" \ + org.opencontainers.image.url="https://github.com/cobaltcore-dev/openstack-hypervisor-operator" \ + org.opencontainers.image.created=${BININFO_BUILD_DATE} \ + org.opencontainers.image.revision=${BININFO_COMMIT_HASH} \ + org.opencontainers.image.version=${BININFO_VERSION} + +USER 4200:4200 +WORKDIR /home/appuser +ENTRYPOINT [ "/usr/bin/manager" ] diff --git a/Makefile b/Makefile index 65f1fd98..518b01b9 100644 --- a/Makefile +++ b/Makefile @@ -1,219 +1,247 @@ -# SPDX-FileCopyrightText: Copyright 2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors -# +################################################################################ +# This file is AUTOGENERATED with # +# Edit Makefile.maker.yaml instead. # +################################################################################ + +# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company # SPDX-License-Identifier: Apache-2.0 -# Image URL to use all building/pushing image targets -IMG ?= keppel.eu-de-1.cloud.sap/ccloud/openstack-hypervisor-operator:latest -# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. -ENVTEST_K8S_VERSION = 1.34.1 - -# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) -ifeq (,$(shell go env GOBIN)) -GOBIN=$(shell go env GOPATH)/bin -else -GOBIN=$(shell go env GOBIN) + +# macOS ships with make 3.81 from 2006, which does not support all the features that we want (e.g. --warn-undefined-variables) +ifeq ($(MAKE_VERSION),3.81) + ifeq (,$(shell which gmake 2>/dev/null)) + $(error We do not support this "make" version ($(MAKE_VERSION)) which is two decades old. Please install a newer version, e.g. using "brew install make") + else + $(error We do not support this "make" version ($(MAKE_VERSION)) which is two decades old. You have a newer GNU make installed, so please run "gmake" instead) + endif endif -# CONTAINER_TOOL defines the container tool to be used for building images. -# Be aware that the target commands are only tested with Docker which is -# scaffolded by default. However, you might want to replace it to use other -# tools. (i.e. podman) -CONTAINER_TOOL ?= docker - -# Setting SHELL to bash allows bash commands to be executed by recipes. -# Options are set to exit when a recipe line exits non-zero or a piped command fails. -SHELL = /usr/bin/env bash -o pipefail -.SHELLFLAGS = -ec - -.PHONY: all -all: build - -##@ General - -# The help target prints out all targets with their descriptions organized -# beneath their categories. The categories are represented by '##@' and the -# target descriptions by '##'. The awk command is responsible for reading the -# entire set of makefiles included in this invocation, looking for lines of the -# file as xyz: ## something, and then pretty-format the target and help. Then, -# if there's a line with ##@ something, that gets pretty-printed as a category. -# More info on the usage of ANSI control characters for terminal formatting: -# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters -# More info on the awk command: -# http://linuxcommand.org/lc3_adv_awk.php - -.PHONY: help -help: ## Display this help. - @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) - -##@ Development - -.PHONY: manifests -manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. - $(CONTROLLER_GEN) \ - paths="./internal/..." \ - rbac:roleName=manager-role crd webhook \ - output:crd:artifacts:config=config/crd/bases - -.PHONY: generate -generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations and applyconfigurations - $(CONTROLLER_GEN) \ - paths="./api/..." \ - object:headerFile="hack/boilerplate.go.txt" \ - applyconfiguration:headerFile="hack/boilerplate.go.txt" - -.PHONY: fmt -fmt: ## Run go fmt against code. - go fmt ./... - -.PHONY: vet -vet: ## Run go vet against code. - go vet ./... - -.PHONY: test -test: manifests generate fmt vet envtest ## Run tests. - KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out - -# Utilize Kind or modify the e2e tests to load the image locally, enabling compatibility with other vendors. -.PHONY: test-e2e # Run the e2e tests against a Kind k8s instance that is spun up. -test-e2e: - go test ./test/e2e/ -v -ginkgo.v - -.PHONY: lint -lint: golangci-lint ## Run golangci-lint linter - $(GOLANGCI_LINT) run - -.PHONY: lint-fix -lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes - $(GOLANGCI_LINT) run --fix - -##@ Build - -.PHONY: build -build: manifests generate fmt vet ## Build manager binary. - go build -o bin/manager cmd/main.go - -.PHONY: run -run: manifests generate fmt vet ## Run a controller from your host. - go run ./cmd/main.go - -# If you wish to build the manager image targeting other platforms you can use the --platform flag. -# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it. -# More info: https://docs.docker.com/develop/develop-images/build_enhancements/ -.PHONY: docker-build -docker-build: ## Build docker image with the manager. - $(CONTAINER_TOOL) build --platform linux/amd64 -t ${IMG} . - -.PHONY: docker-push -docker-push: ## Push docker image with the manager. - $(CONTAINER_TOOL) push ${IMG} - -# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple -# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to: -# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/ -# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/ -# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=> then the export will fail) -# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option. -PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le -.PHONY: docker-buildx -docker-buildx: ## Build and push docker image for the manager for cross-platform support - # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile - sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross - - $(CONTAINER_TOOL) buildx create --name openstack-hypervisor-operator-builder - $(CONTAINER_TOOL) buildx use openstack-hypervisor-operator-builder - - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross . - - $(CONTAINER_TOOL) buildx rm openstack-hypervisor-operator-builder - rm Dockerfile.cross - -.PHONY: build-installer -build-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment. - mkdir -p dist - cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} - $(KUSTOMIZE) build config/default > dist/install.yaml - -##@ Deployment - -ifndef ignore-not-found - ignore-not-found = false +MAKEFLAGS=--warn-undefined-variables +# /bin/sh is dash on Debian which does not support all features of ash/bash +# to fix that we use /bin/bash only on Debian to not break Alpine +ifneq (,$(wildcard /etc/os-release)) # check file existence + ifneq ($(shell grep -c debian /etc/os-release),0) + SHELL := /bin/bash + endif endif +UNAME_S := $(shell uname -s) +SED = sed +XARGS = xargs +ifeq ($(UNAME_S),Darwin) + SED = gsed + XARGS = gxargs +endif + +default: build-all -.PHONY: install -install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. - $(KUSTOMIZE) build config/crd | $(KUBECTL) apply -f - - -.PHONY: uninstall -uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. - $(KUSTOMIZE) build config/crd | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - - -.PHONY: deploy -deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. - cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} - $(KUSTOMIZE) build config/default | $(KUBECTL) apply -f - - -.PHONY: undeploy -undeploy: kustomize ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. - $(KUSTOMIZE) build config/default | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - - -##@ Dependencies - -## Location to install dependencies to -LOCALBIN ?= $(shell pwd)/bin -$(LOCALBIN): - mkdir -p $(LOCALBIN) - -## Tool Binaries -KUBECTL ?= kubectl -KUSTOMIZE ?= $(LOCALBIN)/kustomize -CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen -ENVTEST ?= $(LOCALBIN)/setup-envtest -GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint -HELMIFY ?= $(LOCALBIN)/helmify - -## Tool Versions -KUSTOMIZE_VERSION ?= v5.7.1 -CONTROLLER_TOOLS_VERSION ?= v0.19.0 -ENVTEST_VERSION ?= release-0.19 -GOLANGCI_LINT_VERSION ?= v2.5.0 -HELMIFY_VERSION ?= v0.4.18 - -.PHONY: kustomize -kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. -$(KUSTOMIZE): $(LOCALBIN) - $(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION)) - -.PHONY: controller-gen -controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. -$(CONTROLLER_GEN): $(LOCALBIN) - $(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION)) - -.PHONY: envtest -envtest: $(ENVTEST) ## Download setup-envtest locally if necessary. -$(ENVTEST): $(LOCALBIN) - $(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION)) - -.PHONY: golangci-lint -golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary. -$(GOLANGCI_LINT): $(LOCALBIN) - $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION)) +.PHONY: install-crds +install-crds: generate ## Install CRDs into the K8s cluster specified in ~/.kube/config. + kubectl kustomize config/crd | kubectl apply -f - .PHONY: helmify -helmify: $(HELMIFY) ## Download helmify locally if necessary. -$(HELMIFY): $(LOCALBIN) - $(call go-install-tool,$(HELMIFY),github.com/arttor/helmify/cmd/helmify,$(HELMIFY_VERSION)) - -helm: manifests kustomize helmify - $(KUSTOMIZE) build config/default | $(HELMIFY) -crd-dir charts/openstack-hypervisor-operator - -# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist -# $1 - target path with name of binary -# $2 - package url which can be installed -# $3 - specific version of package -define go-install-tool -@[ -f "$(1)-$(3)" ] || { \ -set -e; \ -package=$(2)@$(3) ;\ -echo "Downloading $${package}" ;\ -rm -f $(1) || true ;\ -GOBIN=$(LOCALBIN) go install $${package} ;\ -mv $(1) $(1)-$(3) ;\ -} ;\ -ln -sf $(1)-$(3) $(1) -endef +helmify: + kubectl kustomize config/default | helmify -crd-dir charts/openstack-hypervisor-operator + +install-goimports: FORCE + @if ! hash goimports 2>/dev/null; then printf "\e[1;36m>> Installing goimports (this may take a while)...\e[0m\n"; go install golang.org/x/tools/cmd/goimports@latest; fi + +install-golangci-lint: FORCE + @if ! hash golangci-lint 2>/dev/null; then printf "\e[1;36m>> Installing golangci-lint (this may take a while)...\e[0m\n"; go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest; fi + +install-modernize: FORCE + @if ! hash modernize 2>/dev/null; then printf "\e[1;36m>> Installing modernize (this may take a while)...\e[0m\n"; go install golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest; fi + +install-shellcheck: FORCE + @if ! hash shellcheck 2>/dev/null; then printf "\e[1;36m>> Installing shellcheck...\e[0m\n"; SHELLCHECK_ARCH=$(shell uname -m); if [[ "$$SHELLCHECK_ARCH" == "arm64" ]]; then SHELLCHECK_ARCH=aarch64; fi; SHELLCHECK_OS=$(shell uname -s | tr '[:upper:]' '[:lower:]'); SHELLCHECK_VERSION="stable"; if command -v curl >/dev/null 2>&1; then GET="curl -sLo-"; elif command -v wget >/dev/null 2>&1; then GET="wget -O-"; else echo "Didn't find curl or wget to download shellcheck"; exit 2; fi; $$GET "https://github.com/koalaman/shellcheck/releases/download/$$SHELLCHECK_VERSION/shellcheck-$$SHELLCHECK_VERSION.$$SHELLCHECK_OS.$$SHELLCHECK_ARCH.tar.xz" | tar -Jxf -; BIN=$$(go env GOBIN); if [[ -z $$BIN ]]; then BIN=$$(go env GOPATH)/bin; fi; install -Dm755 shellcheck-$$SHELLCHECK_VERSION/shellcheck -t "$$BIN"; rm -rf shellcheck-$$SHELLCHECK_VERSION; fi + +install-go-licence-detector: FORCE + @if ! hash go-licence-detector 2>/dev/null; then printf "\e[1;36m>> Installing go-licence-detector (this may take a while)...\e[0m\n"; go install go.elastic.co/go-licence-detector@latest; fi + +install-addlicense: FORCE + @if ! hash addlicense 2>/dev/null; then printf "\e[1;36m>> Installing addlicense (this may take a while)...\e[0m\n"; go install github.com/google/addlicense@latest; fi + +install-reuse: FORCE + @if ! hash reuse 2>/dev/null; then if ! hash pip3 2>/dev/null; then printf "\e[1;31m>> Cannot install reuse because no pip3 was found. Either install it using your package manager or install pip3\e[0m\n"; else printf "\e[1;36m>> Installing reuse...\e[0m\n"; pip3 install --user reuse; fi; fi + +prepare-static-check: FORCE install-golangci-lint install-modernize install-shellcheck install-go-licence-detector install-addlicense install-reuse + +install-controller-gen: FORCE + @if ! hash controller-gen 2>/dev/null; then printf "\e[1;36m>> Installing controller-gen (this may take a while)...\e[0m\n"; go install sigs.k8s.io/controller-tools/cmd/controller-gen@latest; fi + +install-setup-envtest: FORCE + @if ! hash setup-envtest 2>/dev/null; then printf "\e[1;36m>> Installing setup-envtest (this may take a while)...\e[0m\n"; go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest; fi + +# To add additional flags or values (before the default ones), specify the variable in the environment, e.g. `GO_BUILDFLAGS='-tags experimental' make`. +# To override the default flags or values, specify the variable on the command line, e.g. `make GO_BUILDFLAGS='-tags experimental'`. +GO_BUILDFLAGS += +GO_LDFLAGS += +GO_TESTFLAGS += +GO_TESTENV += +GO_BUILDENV += + +build-all: build/manager + +build/manager: FORCE generate + env $(GO_BUILDENV) go build $(GO_BUILDFLAGS) -ldflags '-s -w $(GO_LDFLAGS)' -o build/manager ./cmd + +DESTDIR = +ifeq ($(shell uname -s),Darwin) + PREFIX = /usr/local +else + PREFIX = /usr +endif + +install: FORCE build/manager + install -d -m 0755 "$(DESTDIR)$(PREFIX)/bin" + install -m 0755 build/manager "$(DESTDIR)$(PREFIX)/bin/manager" + +# which packages to test with test runner +GO_TESTPKGS := $(shell go list -f '{{if or .TestGoFiles .XTestGoFiles}}{{.Dir}}{{end}}' ./...) +ifeq ($(GO_TESTPKGS),) +GO_TESTPKGS := ./... +endif +# which packages to measure coverage for +GO_COVERPKGS := $(shell go list ./... | grep -E '/internal') +# to get around weird Makefile syntax restrictions, we need variables containing nothing, a space and comma +null := +space := $(null) $(null) +comma := , + +check: FORCE static-check build/cover.html build-all + @printf "\e[1;32m>> All checks successful.\e[0m\n" + +generate: install-controller-gen + @printf "\e[1;36m>> controller-gen\e[0m\n" + @controller-gen crd rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases + @controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..." + @controller-gen applyconfiguration paths="./..." + +run-golangci-lint: FORCE install-golangci-lint + @printf "\e[1;36m>> golangci-lint\e[0m\n" + @golangci-lint config verify + @golangci-lint run + +run-modernize: FORCE install-modernize + @printf "\e[1;36m>> modernize\e[0m\n" + @modernize $(GO_TESTPKGS) + +run-shellcheck: FORCE install-shellcheck + @printf "\e[1;36m>> shellcheck\e[0m\n" + @find . -type f \( -name '*.bash' -o -name '*.ksh' -o -name '*.zsh' -o -name '*.sh' -o -name '*.shlib' \) -exec shellcheck {} + + +build/cover.out: FORCE generate install-setup-envtest | build + @printf "\e[1;36m>> Running tests\e[0m\n" + KUBEBUILDER_ASSETS=$$(setup-envtest use 1.34 -p path) go run github.com/onsi/ginkgo/v2/ginkgo run --randomize-all -output-dir=build $(GO_BUILDFLAGS) -ldflags '-s -w $(GO_LDFLAGS)' -covermode=count -coverpkg=$(subst $(space),$(comma),$(GO_COVERPKGS)) $(GO_TESTFLAGS) $(GO_TESTPKGS) + @awk < build/coverprofile.out '$$1 != "mode:" { is_filename[$$1] = true; counts1[$$1]+=$$2; counts2[$$1]+=$$3 } END { for (filename in is_filename) { printf "%s %d %d\n", filename, counts1[filename], counts2[filename]; } }' | sort | $(SED) '1s/^/mode: count\n/' > $@ + +build/cover.html: build/cover.out + @printf "\e[1;36m>> go tool cover > build/cover.html\e[0m\n" + @go tool cover -html $< -o $@ + +check-addlicense: FORCE install-addlicense + @printf "\e[1;36m>> addlicense --check\e[0m\n" + @addlicense --check -- $(patsubst $(shell awk '$$1 == "module" {print $$2}' go.mod)%,.%/*.go,$(shell go list ./...)) + +check-reuse: FORCE install-reuse + @printf "\e[1;36m>> reuse lint\e[0m\n" + @if ! reuse lint -q; then reuse lint; fi + +check-license-headers: FORCE check-addlicense check-reuse + +__static-check: FORCE run-shellcheck run-golangci-lint run-modernize check-dependency-licenses check-license-headers + +static-check: FORCE + @$(MAKE) --keep-going --no-print-directory __static-check + +build: + @mkdir $@ + +tidy-deps: FORCE + go mod tidy + go mod verify + +license-headers: FORCE install-addlicense install-reuse + @printf "\e[1;36m>> addlicense (for license headers on source code files)\e[0m\n" + @printf "%s\0" $(patsubst $(shell awk '$$1 == "module" {print $$2}' go.mod)%,.%/*.go,$(shell go list ./...)) | $(XARGS) -0 -I{} bash -c 'year="$$(grep 'Copyright' {} | head -n1 | grep -E -o '"'"'[0-9]{4}(-[0-9]{4})?'"'"')"; if [[ -z "$$year" ]]; then year=$$(date +%Y); fi; gawk -i inplace '"'"'{if (display) {print} else {!/^\/\*/ && !/^\*/}}; {if (!display && $$0 ~ /^(package |$$)/) {display=1} else { }}'"'"' {}; addlicense -c "SAP SE or an SAP affiliate company" -s=only -y "$$year" -- {}; $(SED) -i '"'"'1s+// Copyright +// SPDX-FileCopyrightText: +'"'"' {}; ' + @printf "\e[1;36m>> reuse annotate (for license headers on other files)\e[0m\n" + @reuse lint -j | jq -r '.non_compliant.missing_licensing_info[]' | grep -vw vendor | $(XARGS) reuse annotate -c 'SAP SE or an SAP affiliate company' -l Apache-2.0 --skip-unrecognised + @printf "\e[1;36m>> reuse download --all\e[0m\n" + @reuse download --all + @printf "\e[1;35mPlease review the changes. If *.license files were generated, consider instructing go-makefile-maker to add overrides to REUSE.toml instead.\e[0m\n" + +check-dependency-licenses: FORCE install-go-licence-detector + @printf "\e[1;36m>> go-licence-detector\e[0m\n" + @go list -m -mod=readonly -json all | go-licence-detector -includeIndirect -rules .license-scan-rules.json -overrides .license-scan-overrides.jsonl + +goimports: FORCE install-goimports + @printf "\e[1;36m>> goimports -w -local https://github.com/cobaltcore-dev/openstack-hypervisor-operator\e[0m\n" + @goimports -w -local github.com/cobaltcore-dev/openstack-hypervisor-operator $(patsubst $(shell awk '$$1 == "module" {print $$2}' go.mod)%,.%/*.go,$(shell go list ./...)) + +modernize: FORCE install-modernize + @printf "\e[1;36m>> modernize -fix ./...\e[0m\n" + @modernize -fix ./... + +clean: FORCE + git clean -dxf build + +vars: FORCE + @printf "DESTDIR=$(DESTDIR)\n" + @printf "GO_BUILDENV=$(GO_BUILDENV)\n" + @printf "GO_BUILDFLAGS=$(GO_BUILDFLAGS)\n" + @printf "GO_COVERPKGS=$(GO_COVERPKGS)\n" + @printf "GO_LDFLAGS=$(GO_LDFLAGS)\n" + @printf "GO_TESTFLAGS=$(GO_TESTFLAGS)\n" + @printf "GO_TESTPKGS=$(GO_TESTPKGS)\n" + @printf "MAKE=$(MAKE)\n" + @printf "MAKE_VERSION=$(MAKE_VERSION)\n" + @printf "PREFIX=$(PREFIX)\n" + @printf "SED=$(SED)\n" + @printf "UNAME_S=$(UNAME_S)\n" + @printf "XARGS=$(XARGS)\n" +help: FORCE + @printf "\n" + @printf "\e[1mUsage:\e[0m\n" + @printf " make \e[36m\e[0m\n" + @printf "\n" + @printf "\e[1mGeneral\e[0m\n" + @printf " \e[36mvars\e[0m Display values of relevant Makefile variables.\n" + @printf " \e[36mhelp\e[0m Display this help.\n" + @printf "\n" + @printf "\e[1mPrepare\e[0m\n" + @printf " \e[36minstall-goimports\e[0m Install goimports required by goimports/static-check\n" + @printf " \e[36minstall-golangci-lint\e[0m Install golangci-lint required by run-golangci-lint/static-check\n" + @printf " \e[36minstall-modernize\e[0m Install modernize required by run-modernize/static-check\n" + @printf " \e[36minstall-shellcheck\e[0m Install shellcheck required by run-shellcheck/static-check\n" + @printf " \e[36minstall-go-licence-detector\e[0m Install-go-licence-detector required by check-dependency-licenses/static-check\n" + @printf " \e[36minstall-addlicense\e[0m Install addlicense required by check-license-headers/license-headers/static-check\n" + @printf " \e[36minstall-reuse\e[0m Install reuse required by license-headers/check-reuse\n" + @printf " \e[36mprepare-static-check\e[0m Install any tools required by static-check. This is used in CI before dropping privileges, you should probably install all the tools using your package manager\n" + @printf " \e[36minstall-controller-gen\e[0m Install controller-gen required by static-check and build-all. This is used in CI before dropping privileges, you should probably install all the tools using your package manager\n" + @printf " \e[36minstall-setup-envtest\e[0m Install setup-envtest required by check. This is used in CI before dropping privileges, you should probably install all the tools using your package manager\n" + @printf "\n" + @printf "\e[1mBuild\e[0m\n" + @printf " \e[36mbuild-all\e[0m Build all binaries.\n" + @printf " \e[36mbuild/manager\e[0m Build manager.\n" + @printf " \e[36minstall\e[0m Install all binaries. This option understands the conventional 'DESTDIR' and 'PREFIX' environment variables for choosing install locations.\n" + @printf "\n" + @printf "\e[1mTest\e[0m\n" + @printf " \e[36mcheck\e[0m Run the test suite (unit tests and golangci-lint).\n" + @printf " \e[36mgenerate\e[0m Generate code for Kubernetes CRDs and deepcopy.\n" + @printf " \e[36mrun-golangci-lint\e[0m Install and run golangci-lint. Installing is used in CI, but you should probably install golangci-lint using your package manager.\n" + @printf " \e[36mrun-modernize\e[0m Install and run modernize. Installing is used in CI, but you should probably install modernize using your package manager.\n" + @printf " \e[36mrun-shellcheck\e[0m Install and run shellcheck. Installing is used in CI, but you should probably install shellcheck using your package manager.\n" + @printf " \e[36mbuild/cover.out\e[0m Run tests and generate coverage report.\n" + @printf " \e[36mbuild/cover.html\e[0m Generate an HTML file with source code annotations from the coverage report.\n" + @printf " \e[36mcheck-addlicense\e[0m Check license headers in all non-vendored .go files with addlicense.\n" + @printf " \e[36mcheck-reuse\e[0m Check reuse compliance\n" + @printf " \e[36mcheck-license-headers\e[0m Run static code checks\n" + @printf " \e[36mstatic-check\e[0m Run static code checks\n" + @printf "\n" + @printf "\e[1mDevelopment\e[0m\n" + @printf " \e[36mtidy-deps\e[0m Run go mod tidy and go mod verify.\n" + @printf " \e[36mlicense-headers\e[0m Add (or overwrite) license headers on all non-vendored source code files.\n" + @printf " \e[36mcheck-dependency-licenses\e[0m Check all dependency licenses using go-licence-detector.\n" + @printf " \e[36mgoimports\e[0m Run goimports on all non-vendored .go files\n" + @printf " \e[36mmodernize\e[0m Run modernize on all non-vendored .go files\n" + @printf " \e[36mclean\e[0m Run git clean.\n" + +.PHONY: FORCE diff --git a/Makefile.maker.yaml b/Makefile.maker.yaml new file mode 100644 index 00000000..cc8b0190 --- /dev/null +++ b/Makefile.maker.yaml @@ -0,0 +1,66 @@ +# Configuration file for + +binaries: + - name: manager + fromPackage: ./cmd + installTo: bin/ + +controllerGen: + enabled: true + crdOutputPath: config/crd/bases + objectHeaderFile: hack/boilerplate.go.txt + rbacRoleName: manager-role + +coverageTest: + only: '/internal' + +dockerfile: + enabled: true + runAsRoot: false + +golang: + autoupdateableDeps: ^github.com/(?:sapcc|sap-cloud-infrastructure|cobaltcore-dev)/ + setGoModVersion: true + +golangciLint: + createConfig: true + +githubWorkflow: + ci: + enabled: true + global: + defaultBranch: main # only defined here so that the "Run go-makefile-maker" Action knows it + securityChecks: + enabled: true + queries: security-extended + +metadata: + url: https://github.com/cobaltcore-dev/openstack-hypervisor-operator + +renovate: + enabled: true + assignees: + - notandy + - fwiesel + - mchristianl + - toanju + +reuse: + annotations: + - paths: + - PROJECT + - config/** + - charts/** + - build/** + - applyconfigurations/** + SPDX-FileCopyrightText: 'SAP SE or an SAP affiliate company and cobaltcore-dev contributors' + SPDX-License-Identifier: Apache-2.0 + +verbatim: | + .PHONY: install-crds + install-crds: generate ## Install CRDs into the K8s cluster specified in ~/.kube/config. + kubectl kustomize config/crd | kubectl apply -f - + + .PHONY: helmify + helmify: + kubectl kustomize config/default | helmify -crd-dir charts/openstack-hypervisor-operator diff --git a/REUSE.toml b/REUSE.toml index f8d33495..2913cc21 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -1,36 +1,31 @@ -# SPDX-FileCopyrightText: Copyright 2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors -# +# SPDX-FileCopyrightText: SAP SE or an SAP affiliate company # SPDX-License-Identifier: Apache-2.0 - version = 1 -SPDX-PackageName = "SAP SE or an SAP affiliate company and cobaltcore-dev contributors" -SPDX-PackageDownloadLocation = "" -SPDX-PackageComment = "The code in this project may include calls to APIs (\"API Calls\") of\n SAP or third-party products or services developed outside of this project\n (\"External Products\").\n \"APIs\" means application programming interfaces, as well as their respective\n specifications and implementing code that allows software to communicate with\n other software.\n API Calls to External Products are not licensed under the open source license\n that governs this project. The use of such API Calls and related External\n Products are subject to applicable additional agreements with the relevant\n provider of the External Products. In no event shall the open source license\n that governs this project grant any rights in or to any External Products,or\n alter, expand or supersede any terms of the applicable additional agreements.\n If you have a valid license agreement with SAP for the use of a particular SAP\n External Product, then you may make use of any API Calls included in this\n project's code for that SAP External Product, subject to the terms of such\n license agreement. If you do not have a valid license agreement for the use of\n a particular SAP External Product, then you may only make use of any API Calls\n in this project for that SAP External Product for your internal, non-productive\n and non-commercial test and evaluation of such API Calls. Nothing herein grants\n you any rights to use or access any SAP External Product, or provide any third\n parties the right to use of access any SAP External Product, through API Calls." +SPDX-PackageName = "openstack-hypervisor-operator" +SPDX-PackageDownloadLocation = "https://github.com/cobaltcore-dev/openstack-hypervisor-operator" [[annotations]] -path = "charts/**" -precedence = "aggregate" -SPDX-FileCopyrightText = "2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors" -SPDX-License-Identifier = "Apache-2.0" - -[[annotations]] -path = "config/**" -precedence = "aggregate" -SPDX-FileCopyrightText = "2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors" -SPDX-License-Identifier = "Apache-2.0" - -[[annotations]] -path = "**/*_mock.go" -precedence = "aggregate" -SPDX-FileCopyrightText = "2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors" +path = [ + ".github/CODEOWNERS", + ".github/renovate.json", + ".gitignore", + ".license-scan-overrides.jsonl", + ".license-scan-rules.json", + "go.mod", + "go.sum", + "Makefile.maker.yaml", + "vendor/modules.txt", +] +SPDX-FileCopyrightText = "SAP SE or an SAP affiliate company" SPDX-License-Identifier = "Apache-2.0" [[annotations]] path = [ - ".github/**", - "PROJECT", - "go.sum" + "PROJECT", + "config/**", + "charts/**", + "build/**", + "applyconfigurations/**", ] -precedence = "aggregate" -SPDX-FileCopyrightText = "2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors" +SPDX-FileCopyrightText = "SAP SE or an SAP affiliate company and cobaltcore-dev contributors" SPDX-License-Identifier = "Apache-2.0" diff --git a/applyconfigurations/api/v1/capabilitiesstatus.go b/applyconfigurations/api/v1/capabilitiesstatus.go index 37d1d1af..0ab1ec77 100644 --- a/applyconfigurations/api/v1/capabilitiesstatus.go +++ b/applyconfigurations/api/v1/capabilitiesstatus.go @@ -1,19 +1,3 @@ -/* -SPDX-FileCopyrightText: Copyright 2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors -SPDX-License-Identifier: Apache-2.0 - -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 controller-gen. DO NOT EDIT. package v1 diff --git a/applyconfigurations/api/v1/eviction.go b/applyconfigurations/api/v1/eviction.go index 2346d8f0..4db6d591 100644 --- a/applyconfigurations/api/v1/eviction.go +++ b/applyconfigurations/api/v1/eviction.go @@ -1,19 +1,3 @@ -/* -SPDX-FileCopyrightText: Copyright 2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors -SPDX-License-Identifier: Apache-2.0 - -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 controller-gen. DO NOT EDIT. package v1 diff --git a/applyconfigurations/api/v1/evictionspec.go b/applyconfigurations/api/v1/evictionspec.go index 334d0dc2..790e92fb 100644 --- a/applyconfigurations/api/v1/evictionspec.go +++ b/applyconfigurations/api/v1/evictionspec.go @@ -1,19 +1,3 @@ -/* -SPDX-FileCopyrightText: Copyright 2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors -SPDX-License-Identifier: Apache-2.0 - -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 controller-gen. DO NOT EDIT. package v1 diff --git a/applyconfigurations/api/v1/evictionstatus.go b/applyconfigurations/api/v1/evictionstatus.go index 07817672..80e7aa1c 100644 --- a/applyconfigurations/api/v1/evictionstatus.go +++ b/applyconfigurations/api/v1/evictionstatus.go @@ -1,19 +1,3 @@ -/* -SPDX-FileCopyrightText: Copyright 2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors -SPDX-License-Identifier: Apache-2.0 - -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 controller-gen. DO NOT EDIT. package v1 diff --git a/applyconfigurations/api/v1/hypervisor.go b/applyconfigurations/api/v1/hypervisor.go index f8172363..18d7aabd 100644 --- a/applyconfigurations/api/v1/hypervisor.go +++ b/applyconfigurations/api/v1/hypervisor.go @@ -1,19 +1,3 @@ -/* -SPDX-FileCopyrightText: Copyright 2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors -SPDX-License-Identifier: Apache-2.0 - -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 controller-gen. DO NOT EDIT. package v1 diff --git a/applyconfigurations/api/v1/hypervisorspec.go b/applyconfigurations/api/v1/hypervisorspec.go index 621cdbcf..d5add0dc 100644 --- a/applyconfigurations/api/v1/hypervisorspec.go +++ b/applyconfigurations/api/v1/hypervisorspec.go @@ -1,19 +1,3 @@ -/* -SPDX-FileCopyrightText: Copyright 2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors -SPDX-License-Identifier: Apache-2.0 - -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 controller-gen. DO NOT EDIT. package v1 diff --git a/applyconfigurations/api/v1/hypervisorstatus.go b/applyconfigurations/api/v1/hypervisorstatus.go index 71d0d047..036b3e65 100644 --- a/applyconfigurations/api/v1/hypervisorstatus.go +++ b/applyconfigurations/api/v1/hypervisorstatus.go @@ -1,19 +1,3 @@ -/* -SPDX-FileCopyrightText: Copyright 2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors -SPDX-License-Identifier: Apache-2.0 - -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 controller-gen. DO NOT EDIT. package v1 diff --git a/applyconfigurations/api/v1/hypervisorupdatestatus.go b/applyconfigurations/api/v1/hypervisorupdatestatus.go index ea4810c3..b0fc398a 100644 --- a/applyconfigurations/api/v1/hypervisorupdatestatus.go +++ b/applyconfigurations/api/v1/hypervisorupdatestatus.go @@ -1,19 +1,3 @@ -/* -SPDX-FileCopyrightText: Copyright 2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors -SPDX-License-Identifier: Apache-2.0 - -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 controller-gen. DO NOT EDIT. package v1 diff --git a/applyconfigurations/api/v1/instance.go b/applyconfigurations/api/v1/instance.go index ebacbe9b..e2bd15ad 100644 --- a/applyconfigurations/api/v1/instance.go +++ b/applyconfigurations/api/v1/instance.go @@ -1,19 +1,3 @@ -/* -SPDX-FileCopyrightText: Copyright 2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors -SPDX-License-Identifier: Apache-2.0 - -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 controller-gen. DO NOT EDIT. package v1 diff --git a/applyconfigurations/api/v1/operatingsystemstatus.go b/applyconfigurations/api/v1/operatingsystemstatus.go index fe5525c4..deaae1dd 100644 --- a/applyconfigurations/api/v1/operatingsystemstatus.go +++ b/applyconfigurations/api/v1/operatingsystemstatus.go @@ -1,19 +1,3 @@ -/* -SPDX-FileCopyrightText: Copyright 2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors -SPDX-License-Identifier: Apache-2.0 - -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 controller-gen. DO NOT EDIT. package v1 diff --git a/applyconfigurations/internal/internal.go b/applyconfigurations/internal/internal.go index 57dc85d6..35ead19a 100644 --- a/applyconfigurations/internal/internal.go +++ b/applyconfigurations/internal/internal.go @@ -1,19 +1,3 @@ -/* -SPDX-FileCopyrightText: Copyright 2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors -SPDX-License-Identifier: Apache-2.0 - -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 controller-gen. DO NOT EDIT. package internal diff --git a/applyconfigurations/utils.go b/applyconfigurations/utils.go index ce8e9f27..3ecf8f68 100644 --- a/applyconfigurations/utils.go +++ b/applyconfigurations/utils.go @@ -1,19 +1,3 @@ -/* -SPDX-FileCopyrightText: Copyright 2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors -SPDX-License-Identifier: Apache-2.0 - -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 controller-gen. DO NOT EDIT. package applyconfigurations diff --git a/go.mod b/go.mod index a9c9c97e..183f96c5 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 module github.com/cobaltcore-dev/openstack-hypervisor-operator -go 1.24.7 +go 1.25 require ( github.com/gophercloud/gophercloud/v2 v2.8.0 diff --git a/internal/controller/aggregates_controller.go b/internal/controller/aggregates_controller.go index c6d65c6f..63ae2756 100644 --- a/internal/controller/aggregates_controller.go +++ b/internal/controller/aggregates_controller.go @@ -177,11 +177,9 @@ func addToAggregate(ctx context.Context, serviceClient *gophercloud.ServiceClien aggs[name] = aggregate } - for _, aggHost := range aggregate.Hosts { - if aggHost == host { - log.Info("Found host in aggregate", "host", host, "name", name) - return nil - } + if slices.Contains(aggregate.Hosts, host) { + log.Info("Found host in aggregate", "host", host, "name", name) + return nil } result, err := aggregates.AddHost(ctx, serviceClient, aggregate.ID, aggregates.AddHostOpts{Host: host}).Extract() diff --git a/internal/controller/decomission_controller_test.go b/internal/controller/decomission_controller_test.go index dead7051..37fa4c45 100644 --- a/internal/controller/decomission_controller_test.go +++ b/internal/controller/decomission_controller_test.go @@ -49,7 +49,7 @@ var _ = Describe("Decommission Controller", func() { resource := &corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: nodeName, - Labels: map[string]string{labelEvictionRequired: "true"}, //nolint:goconst + Labels: map[string]string{labelEvictionRequired: "true"}, }, } Expect(k8sClient.Create(ctx, resource)).To(Succeed()) diff --git a/internal/controller/eviction_controller.go b/internal/controller/eviction_controller.go index 2a765d73..760afafa 100644 --- a/internal/controller/eviction_controller.go +++ b/internal/controller/eviction_controller.go @@ -21,7 +21,6 @@ import ( "context" "errors" "fmt" - "math/rand" "net/http" "strings" "time" @@ -47,7 +46,6 @@ type EvictionReconciler struct { client.Client Scheme *runtime.Scheme computeClient *gophercloud.ServiceClient - rand *rand.Rand } const ( @@ -80,7 +78,7 @@ func (r *EvictionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c if !eviction.DeletionTimestamp.IsZero() { err := r.handleFinalizer(ctx, eviction) if err != nil { - if errors.Is(err, ErrorRetry) { + if errors.Is(err, ErrRetry) { return ctrl.Result{RequeueAfter: defaultWaitTime}, nil } return ctrl.Result{}, err @@ -99,14 +97,17 @@ func (r *EvictionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c // We just checked if the condition is there, so this should never // be reached, but let's cover our bass return ctrl.Result{RequeueAfter: 1 * time.Second}, nil - } else if statusCondition.Status == metav1.ConditionTrue { + } + + switch statusCondition.Status { + case metav1.ConditionTrue: // We are running, so we need to evict the next instance return r.handleRunning(ctx, eviction) - } else if statusCondition.Status == metav1.ConditionFalse { + case metav1.ConditionFalse: // We are done, so we can just return log.Info("finished") return ctrl.Result{}, nil - } else { + default: log. WithValues("reason", statusCondition.Reason). WithValues("msg", statusCondition.Message). @@ -304,7 +305,7 @@ func (r *EvictionReconciler) evictNext(ctx context.Context, eviction *kvmv1.Evic return ctrl.Result{}, r.Status().Update(ctx, eviction) } - if vm.TaskState == "deleting" { + if vm.TaskState == "deleting" { //nolint:gocritic // We just have to wait for it to be gone. Try the next one. copy((*instances)[1:], (*instances)[:len(*instances)-1]) (*instances)[0] = uuid @@ -429,17 +430,16 @@ func (r *EvictionReconciler) enableHypervisorService(ctx context.Context, evicti } } - return ErrorRetry + return ErrRetry } } if hypervisor.Service.DisabledReason != r.evictionReason(eviction) { changed := meta.SetStatusCondition(&eviction.Status.Conditions, metav1.Condition{ - Type: kvmv1.ConditionTypeHypervisorReEnabled, - Status: metav1.ConditionTrue, - Message: fmt.Sprintf("Hypervisor already re-enabled for reason: %s", - hypervisor.Service.DisabledReason), - Reason: kvmv1.ConditionReasonSucceeded, + Type: kvmv1.ConditionTypeHypervisorReEnabled, + Status: metav1.ConditionTrue, + Message: "Hypervisor already re-enabled for reason:" + hypervisor.Service.DisabledReason, + Reason: kvmv1.ConditionReasonSucceeded, }) if changed { return r.Status().Update(ctx, eviction) @@ -465,7 +465,7 @@ func (r *EvictionReconciler) enableHypervisorService(ctx context.Context, evicti log.Error(err, "failed to store error message in condition", "message", errorMessage) } } - return ErrorRetry + return ErrRetry } else { changed := meta.SetStatusCondition(&eviction.Status.Conditions, metav1.Condition{ Type: kvmv1.ConditionTypeHypervisorReEnabled, @@ -479,7 +479,6 @@ func (r *EvictionReconciler) enableHypervisorService(ctx context.Context, evicti return nil } } - } // disableHypervisor disables the hypervisor service and adds a finalizer to the eviction @@ -574,6 +573,7 @@ func (r *EvictionReconciler) coldMigrate(ctx context.Context, uuid string, evict // addCondition adds a condition to the Eviction status and updates the status func (r *EvictionReconciler) addCondition(ctx context.Context, eviction *kvmv1.Eviction, status metav1.ConditionStatus, message string, reason string) bool { + if !meta.SetStatusCondition(&eviction.Status.Conditions, metav1.Condition{ Type: kvmv1.ConditionTypeEvicting, Status: status, @@ -602,8 +602,6 @@ func (r *EvictionReconciler) SetupWithManager(mgr ctrl.Manager) error { } r.computeClient.Microversion = "2.90" // Xena (or later) - r.rand = rand.New(rand.NewSource(time.Now().UnixNano())) - return ctrl.NewControllerManagedBy(mgr). Named(EvictionControllerName). For(&kvmv1.Eviction{}). diff --git a/internal/controller/eviction_controller_test.go b/internal/controller/eviction_controller_test.go index 3b336f1f..d1cf3d77 100644 --- a/internal/controller/eviction_controller_test.go +++ b/internal/controller/eviction_controller_test.go @@ -20,7 +20,6 @@ package controller import ( "context" "fmt" - "math/rand" "net/http" "github.com/gophercloud/gophercloud/v2/testhelper" @@ -98,7 +97,7 @@ var _ = Describe("Eviction Controller", func() { fakeServer testhelper.FakeServer ) - ctx := context.Background() + ctx := context.Background() //nolint:govet BeforeEach(func() { By("Setting up the OpenStack http mock server") @@ -213,7 +212,6 @@ var _ = Describe("Eviction Controller", func() { Client: k8sClient, Scheme: k8sClient.Scheme(), computeClient: client.ServiceClient(fakeServer), - rand: rand.New(rand.NewSource(42)), } }) @@ -227,7 +225,7 @@ var _ = Describe("Eviction Controller", func() { }) It("should fail reconciliation", func() { - for i := 0; i < 3; i++ { + for range 3 { _, err := controllerReconciler.Reconcile(ctx, reconcileRequest) Expect(err).NotTo(HaveOccurred()) } @@ -365,7 +363,7 @@ var _ = Describe("Eviction Controller", func() { Expect(ctrlRuntimeClient.IgnoreAlreadyExists(k8sClient.Create(ctx, hypervisor))).To(Succeed()) }) It("should succeed the reconciliation", func() { - for i := 0; i < 3; i++ { + for range 3 { _, err := controllerReconciler.Reconcile(ctx, reconcileRequest) Expect(err).NotTo(HaveOccurred()) } diff --git a/internal/controller/hypervisor_controller.go b/internal/controller/hypervisor_controller.go index 5d647d29..d8eceae0 100644 --- a/internal/controller/hypervisor_controller.go +++ b/internal/controller/hypervisor_controller.go @@ -187,7 +187,7 @@ func updateLabelsAndAnnotations(node *metav1.ObjectMeta, hypervisor *kvmv1.Hyper if aggregates, found := node.Annotations[annotationAggregates]; found { // split aggregates string hypervisor.Spec.Aggregates = slices.Collect(func(yield func(string) bool) { - for _, agg := range strings.Split(aggregates, ",") { + for agg := range strings.SplitSeq(aggregates, ",") { trimmed := strings.TrimSpace(agg) if trimmed != "" && !yield(trimmed) { return @@ -200,7 +200,7 @@ func updateLabelsAndAnnotations(node *metav1.ObjectMeta, hypervisor *kvmv1.Hyper if customTraits, found := node.Annotations[annotationCustomTraits]; found { // split custom traits string hypervisor.Spec.CustomTraits = slices.Collect(func(yield func(string) bool) { - for _, trait := range strings.Split(customTraits, ",") { + for trait := range strings.SplitSeq(customTraits, ",") { trimmed := strings.TrimSpace(trait) if trimmed != "" && !yield(trimmed) { return diff --git a/internal/controller/hypervisor_controller_test.go b/internal/controller/hypervisor_controller_test.go index a7a8d64e..ccfca561 100644 --- a/internal/controller/hypervisor_controller_test.go +++ b/internal/controller/hypervisor_controller_test.go @@ -48,7 +48,7 @@ var _ = Describe("Hypervisor Controller", func() { resource = &corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: "other-node", - Labels: map[string]string{corev1.LabelTopologyZone: "test-zone"}, //nolint:goconst + Labels: map[string]string{corev1.LabelTopologyZone: "test-zone"}, }, } }) @@ -123,7 +123,7 @@ var _ = Describe("Hypervisor Controller", func() { Expect(k8sClient.Status().Update(ctx, resource)).To(Succeed()) By("Reconciling the created resource") - for i := 0; i < 3; i++ { + for range 3 { _, err := hypervisorController.Reconcile(ctx, ctrl.Request{ NamespacedName: types.NamespacedName{Name: resource.Name}, }) diff --git a/internal/controller/maintenance_controller.go b/internal/controller/maintenance_controller.go index 1ed8d022..b1cb833f 100644 --- a/internal/controller/maintenance_controller.go +++ b/internal/controller/maintenance_controller.go @@ -179,7 +179,7 @@ func (r *MaintenanceController) ensureSignallingDeployment(ctx context.Context, deployment.Labels = labels podLabels := maps.Clone(labels) - podLabels[labelCriticalComponent] = "true" //nolint:goconst + podLabels[labelCriticalComponent] = "true" var command []string if ready { diff --git a/internal/controller/maintenance_controller_test.go b/internal/controller/maintenance_controller_test.go index 82df55ae..7ef92560 100644 --- a/internal/controller/maintenance_controller_test.go +++ b/internal/controller/maintenance_controller_test.go @@ -45,7 +45,7 @@ var _ = Describe("Maintenance Controller", func() { resource := &corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: nodeName, - Labels: map[string]string{labelEvictionRequired: "true"}, //nolint:goconst + Labels: map[string]string{labelEvictionRequired: "true"}, }, } Expect(k8sClient.Create(ctx, resource)).To(Succeed()) diff --git a/internal/controller/node_certificate_controller.go b/internal/controller/node_certificate_controller.go index 2c28f3ed..df970db8 100644 --- a/internal/controller/node_certificate_controller.go +++ b/internal/controller/node_certificate_controller.go @@ -47,9 +47,9 @@ type NodeCertificateController struct { issuerName string } -func getSecretAndCertName(name string) (string, string) { - certName := fmt.Sprintf("libvirt-%s", name) - secretName := fmt.Sprintf("tls-%s", certName) +func getSecretAndCertName(name string) (secretName, certName string) { + certName = "libvirt-" + name + secretName = "tls-" + certName return secretName, certName } diff --git a/internal/controller/node_certificate_controller_test.go b/internal/controller/node_certificate_controller_test.go index 81ed1487..1e0943da 100644 --- a/internal/controller/node_certificate_controller_test.go +++ b/internal/controller/node_certificate_controller_test.go @@ -32,7 +32,7 @@ import ( var _ = Describe("Node Certificate Controller", func() { var nodeCertificateController *NodeCertificateController - var k8sClient client.Client + var fakeClient client.Client const ( nodeName = "random-node" issuerName = "test-issuer" @@ -50,32 +50,32 @@ var _ = Describe("Node Certificate Controller", func() { // We need to use the fake client because the envtest environment does include // cert-manager CRDs out of the box. By("Creating the fake client") - k8sClient = fake.NewClientBuilder().WithScheme(scheme).Build() + fakeClient = fake.NewClientBuilder().WithScheme(scheme).Build() nodeCertificateController = &NodeCertificateController{ - Client: k8sClient, - Scheme: k8sClient.Scheme(), + Client: fakeClient, + Scheme: fakeClient.Scheme(), issuerName: issuerName, namespace: namespace, } By("creating the namespace for the reconciler") ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} - Expect(client.IgnoreAlreadyExists(k8sClient.Create(ctx, ns))).To(Succeed()) + Expect(client.IgnoreAlreadyExists(fakeClient.Create(ctx, ns))).To(Succeed()) By("creating the core resource for the Kind Node") resource := &corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: nodeName, - Labels: map[string]string{labelHypervisor: "test"}, //nolint:goconst + Labels: map[string]string{labelHypervisor: "test"}, }, } - Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + Expect(fakeClient.Create(ctx, resource)).To(Succeed()) }) AfterEach(func() { node := &corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}} By("Cleanup the specific node") - Expect(client.IgnoreAlreadyExists(k8sClient.Delete(ctx, node))).To(Succeed()) + Expect(client.IgnoreAlreadyExists(fakeClient.Delete(ctx, node))).To(Succeed()) By("Cleaning up the test environment") }) @@ -94,7 +94,7 @@ var _ = Describe("Node Certificate Controller", func() { By("Checking if the certificate was created") _, certName := getSecretAndCertName(nodeName) certificate := &cmapi.Certificate{} - err = k8sClient.Get(ctx, types.NamespacedName{Name: certName, Namespace: namespace}, certificate) + err = fakeClient.Get(ctx, types.NamespacedName{Name: certName, Namespace: namespace}, certificate) Expect(err).NotTo(HaveOccurred()) Expect(certificate.Spec.IssuerRef.Name).To(Equal(issuerName)) Expect(certificate.Spec.DNSNames).To(ContainElement(nodeName)) diff --git a/internal/controller/node_eviction_label_controller.go b/internal/controller/node_eviction_label_controller.go index 5d539c20..dd841920 100644 --- a/internal/controller/node_eviction_label_controller.go +++ b/internal/controller/node_eviction_label_controller.go @@ -97,7 +97,7 @@ func (r *NodeEvictionLabelReconciler) Reconcile(ctx context.Context, req ctrl.Re if !HasStatusCondition(hv.Status.Conditions, ConditionTypeOnboarding) { // Hasn't even started to onboard that node, so nothing to evict for sure - value = "true" //nolint:goconst + value = "true" } else { // check for existing eviction, else create it value, err = r.reconcileEviction(ctx, eviction, node, hostname, maintenanceValue) @@ -113,7 +113,7 @@ func (r *NodeEvictionLabelReconciler) Reconcile(ctx context.Context, req ctrl.Re } newNode := node.DeepCopy() - if value == "true" { //nolint:goconst + if value == "true" { evictAgentsLabels(newNode.Labels) } newNode.Labels[labelEvictionApproved] = maintenanceValue @@ -126,7 +126,7 @@ func (r *NodeEvictionLabelReconciler) Reconcile(ctx context.Context, req ctrl.Re return ctrl.Result{}, k8sclient.IgnoreNotFound(err) } -func (r *NodeEvictionLabelReconciler) reconcileEviction(ctx context.Context, eviction *kvmv1.Eviction, node *corev1.Node, hostname string, maintenanceValue string) (string, error) { +func (r *NodeEvictionLabelReconciler) reconcileEviction(ctx context.Context, eviction *kvmv1.Eviction, node *corev1.Node, hostname, maintenanceValue string) (string, error) { log := logger.FromContext(ctx) if err := r.Get(ctx, k8sclient.ObjectKeyFromObject(eviction), eviction); err != nil { if !k8serrors.IsNotFound(err) { @@ -153,7 +153,7 @@ func (r *NodeEvictionLabelReconciler) reconcileEviction(ctx context.Context, evi } switch evictionState { case "Succeeded": - return "true", nil //nolint:goconst + return "true", nil case "Failed": return "false", nil default: diff --git a/internal/controller/node_eviction_label_controller_test.go b/internal/controller/node_eviction_label_controller_test.go index 81e4b214..af18f55d 100644 --- a/internal/controller/node_eviction_label_controller_test.go +++ b/internal/controller/node_eviction_label_controller_test.go @@ -49,7 +49,7 @@ var _ = Describe("Node Eviction Label Controller", func() { ) Context("When reconciling a node", func() { - ctx := context.Background() + ctx := context.Background() //nolint:govet reconcileNodeLoop := func(steps int) (res ctrl.Result, err error) { for range steps { diff --git a/internal/controller/onboarding_controller.go b/internal/controller/onboarding_controller.go index 0616f011..df4283a8 100644 --- a/internal/controller/onboarding_controller.go +++ b/internal/controller/onboarding_controller.go @@ -48,7 +48,7 @@ import ( "github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/openstack" ) -var errRequeue = fmt.Errorf("requeue requested") +var errRequeue = errors.New("requeue requested") const ( defaultWaitTime = 1 * time.Minute @@ -451,13 +451,13 @@ func (r *OnboardingController) createOrGetTestServer(ctx context.Context, zone, if err != nil { return nil, err } - flavors, err := flavors.ExtractFlavors(flavorPages) + extractedFlavors, err := flavors.ExtractFlavors(flavorPages) if err != nil { return nil, err } var flavorRef string - for _, flavor := range flavors { + for _, flavor := range extractedFlavors { if flavor.Name == testFlavorName { flavorRef = flavor.ID break @@ -465,7 +465,7 @@ func (r *OnboardingController) createOrGetTestServer(ctx context.Context, zone, } if flavorRef == "" { - return nil, fmt.Errorf("couldn't find flavor") + return nil, errors.New("couldn't find flavor") } var imageRef string @@ -488,7 +488,7 @@ func (r *OnboardingController) createOrGetTestServer(ctx context.Context, zone, } if imageRef == "" { - return nil, fmt.Errorf("couldn't find image") + return nil, errors.New("couldn't find image") } falseVal := false @@ -497,19 +497,19 @@ func (r *OnboardingController) createOrGetTestServer(ctx context.Context, zone, return nil, err } - networks, err := networks.ExtractNetworks(networkPages) + extractedNetworks, err := networks.ExtractNetworks(networkPages) if err != nil { return nil, err } var networkRef string - for _, network := range networks { + for _, network := range extractedNetworks { networkRef = network.ID break } if networkRef == "" { - return nil, fmt.Errorf("couldn't find network") + return nil, errors.New("couldn't find network") } log.Info("creating server", "name", serverName) @@ -537,7 +537,7 @@ func (r *OnboardingController) createOrGetTestServer(ctx context.Context, zone, } if server == nil { - return nil, fmt.Errorf("server is nil") + return nil, errors.New("server is nil") } // Apparently the response doesn't contain the value server.Name = serverName diff --git a/internal/controller/onboarding_controller_test.go b/internal/controller/onboarding_controller_test.go index 207f21f0..a33a43ec 100644 --- a/internal/controller/onboarding_controller_test.go +++ b/internal/controller/onboarding_controller_test.go @@ -38,7 +38,7 @@ var _ = Describe("Onboarding Controller", func() { Context("When reconciling a hypervisor", func() { const hypervisorName = "some-test" - ctx := context.Background() + ctx := context.Background() //nolint:govet reconcileLoop := func(steps int) (res ctrl.Result, err error) { req := ctrl.Request{ diff --git a/internal/controller/utils.go b/internal/controller/utils.go index 9c6147ae..b351e9de 100644 --- a/internal/controller/utils.go +++ b/internal/controller/utils.go @@ -22,6 +22,7 @@ import ( "context" "errors" "fmt" + "io" "maps" "net/http" "os" @@ -66,10 +67,14 @@ func updateInstanceHA(node *corev1.Node, data string, acceptedCodes []int) error } url := InstanceHaUrl(region, zone, hostname) - resp, err := http.Post(url, "application/json", bytes.NewBuffer([]byte(data))) + // G107: Potential HTTP request made with variable url + resp, err := http.Post(url, "application/json", bytes.NewBuffer([]byte(data))) //nolint:gosec,bodyclose if err != nil { return fmt.Errorf("failed to send request to ha service due to %w", err) } + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) if !slices.Contains(acceptedCodes, resp.StatusCode) { return fmt.Errorf("ha service answered with unexpected response %v for %v from %v", resp.StatusCode, data, url) } @@ -130,4 +135,4 @@ func Difference[S ~[]E, E comparable](s1, s2 S) S { return diff } -var ErrorRetry = errors.New("ErrorRetry") +var ErrRetry = errors.New("ErrRetry") diff --git a/internal/openstack/hypervisor.go b/internal/openstack/hypervisor.go index fe8e69f9..1615b348 100644 --- a/internal/openstack/hypervisor.go +++ b/internal/openstack/hypervisor.go @@ -19,7 +19,7 @@ package openstack import ( "context" - "fmt" + "errors" "net/http" "github.com/gophercloud/gophercloud/v2" @@ -30,8 +30,8 @@ type HyperVisorsDetails struct { Hypervisors []hypervisors.Hypervisor `json:"hypervisors"` } -var ErrNoHypervisor = fmt.Errorf("no hypervisor found") -var ErrMultipleHypervisors = fmt.Errorf("multiple hypervisors found") +var ErrNoHypervisor = errors.New("no hypervisor found") +var ErrMultipleHypervisors = errors.New("multiple hypervisors found") func GetHypervisorByName(ctx context.Context, sc *gophercloud.ServiceClient, hypervisorHostnamePattern string, withServers bool) (*hypervisors.Hypervisor, error) { listOpts := hypervisors.ListOpts{ diff --git a/internal/openstack/placement.go b/internal/openstack/placement.go index 92215064..d3887c00 100644 --- a/internal/openstack/placement.go +++ b/internal/openstack/placement.go @@ -19,6 +19,7 @@ package openstack import ( "context" + "errors" "fmt" "github.com/gophercloud/gophercloud/v2" @@ -63,7 +64,7 @@ func UpdateTraits(ctx context.Context, client *gophercloud.ServiceClient, resour r.Err = err return } - resp, err := client.Put(ctx, getResourceProviderTraitsURL(client, resourceProviderID), b, &r.Body, &gophercloud.RequestOpts{ + resp, err := client.Put(ctx, getResourceProviderTraitsURL(client, resourceProviderID), b, &r.Body, &gophercloud.RequestOpts{ //nolint:bodyclose OkCodes: []int{200}, }) _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) @@ -97,7 +98,7 @@ func (r ListAllocationsResult) Extract() (*ConsumerAllocations, error) { // List Allocations for a certain consumer func ListAllocations(ctx context.Context, client *gophercloud.ServiceClient, consumerID string) (r ListAllocationsResult) { - resp, err := client.Get(ctx, getAllocationsURL(client, consumerID), nil, &gophercloud.RequestOpts{ + resp, err := client.Get(ctx, getAllocationsURL(client, consumerID), nil, &gophercloud.RequestOpts{ //nolint:bodyclose OkCodes: []int{200}, }) if err != nil { @@ -111,7 +112,7 @@ func ListAllocations(ctx context.Context, client *gophercloud.ServiceClient, con // Delete all Allocations for a certain consumer func DeleteConsumerAllocations(ctx context.Context, client *gophercloud.ServiceClient, consumerID string) (r ListAllocationsResult) { - resp, err := client.Delete(ctx, getAllocationsURL(client, consumerID), &gophercloud.RequestOpts{ + resp, err := client.Delete(ctx, getAllocationsURL(client, consumerID), &gophercloud.RequestOpts{ //nolint:bodyclose OkCodes: []int{204, 404}, }) if err != nil { @@ -146,7 +147,7 @@ func CleanupResourceProvider(ctx context.Context, client *gophercloud.ServiceCli } if len(consumerAllocations.Allocations) > 0 { - return fmt.Errorf("cannot clean up provider, cannot handle non-empty consumer allocations") + return errors.New("cannot clean up provider, cannot handle non-empty consumer allocations") } // The consumer actually doesn't have *any* allocations, so it is just diff --git a/shell.nix b/shell.nix new file mode 100644 index 00000000..8178dbea --- /dev/null +++ b/shell.nix @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company +# SPDX-License-Identifier: Apache-2.0 + +{ pkgs ? import { } }: + +with pkgs; + +mkShell { + nativeBuildInputs = [ + addlicense + ginkgo + go-licence-detector + go_1_25 + golangci-lint + gotools # goimports + kubernetes-controller-tools # controller-gen + renovate + reuse + setup-envtest + # keep this line if you use bash + bashInteractive + ]; +}