diff --git a/.dockerignore b/.dockerignore index 9c5e439..723093e 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 new file mode 100644 index 0000000..46ace68 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: SAP SE or an SAP affiliate company +# SPDX-License-Identifier: Apache-2.0 + +root = true + +[*] +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[{Makefile,go.mod,go.sum,*.go}] +indent_style = tab +indent_size = unset + +[*.md] +trim_trailing_whitespace = false + +[{LICENSE,LICENSES/*,vendor/**}] +charset = unset +end_of_line = unset +indent_size = unset +indent_style = unset +insert_final_newline = unset +trim_trailing_whitespace = unset diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..a40910f --- /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/renovate.json b/.github/renovate.json index 17d11a3..8fe8c0c 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,18 +1,76 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ - "config:base" + "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": [ { - "matchUpdateTypes": ["minor", "patch", "pin", "digest"], - "automerge": true + "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", + "major" + ], + "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 33fc3fc..40c58b6 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -1,48 +1,58 @@ +################################################################################ +# 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 runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Go - uses: actions/setup-go@v5 - 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 + uses: actions/setup-go@v6 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 0000000..083c2a0 --- /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 0000000..bb1e370 --- /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 41088c3..0000000 --- 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@v4 - - name: Set up Go - uses: actions/setup-go@v5 - 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 bb1fe51..0000000 --- a/.github/workflows/golangci-lint.yml +++ /dev/null @@ -1,46 +0,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@v4 - - uses: actions/setup-go@v5 - 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 f998a13..0000000 --- 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@v4 - - name: REUSE Compliance Check - uses: fsfe/reuse-action@v4.0.0 diff --git a/.gitignore b/.gitignore index 244e88b..df1f9ee 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ *.so *.dylib bin/* +build/* Dockerfile.cross # Test binary, built with `go test -c` diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..daff948 --- /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/kvm-node-agent + 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/.license-scan-overrides.jsonl b/.license-scan-overrides.jsonl new file mode 100644 index 0000000..0a8feb2 --- /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 0000000..909cc0f --- /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 9575609..45cc23c 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.2.1 - hooks: - - id: golangci-lint - repo: https://github.com/dnephin/pre-commit-golang rev: v0.5.1 hooks: @@ -32,16 +28,16 @@ repos: 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 a56835f..ab3edce 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,38 +1,38 @@ -# 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/kvm-node-agent" -WORKDIR / -COPY --from=builder /workspace/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 + +# 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/kvm-node-agent" \ + org.opencontainers.image.url="https://github.com/cobaltcore-dev/kvm-node-agent" \ + org.opencontainers.image.created=${BININFO_BUILD_DATE} \ + org.opencontainers.image.revision=${BININFO_COMMIT_HASH} \ + org.opencontainers.image.version=${BININFO_VERSION} + # Hardcoded to kvm-node-agent user/group on host USER 42438:42438 - -ENTRYPOINT ["/manager"] +WORKDIR / +ENTRYPOINT [ "/usr/bin/manager" ] diff --git a/Makefile b/Makefile index 7537d0e..885920f 100644 --- a/Makefile +++ b/Makefile @@ -1,234 +1,263 @@ -# 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 -TAG ?= latest -IMG ?= keppel.eu-de-1.cloud.sap/ccloud/kvm-node-agent:$(TAG) -OCI ?= oci://keppel.eu-de-1.cloud.sap/ccloud-helm -# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. -ENVTEST_K8S_VERSION = 1.30.0 +# 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 -# 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) +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 -# 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 +default: build-all -# 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 +# Generate mock files for all interfaces +INTERFACES = $(patsubst %.go,%_mock.go,$(wildcard internal/**/interface.go)) +mocks: $(INTERFACES) ## Generate mocks for interfaces. +.PHONY: FORCE +FORCE: +%_mock.go: %.go FORCE ## Generate interface mocks with https://github.com/matryer/moq + moq -rm -out $@ $(dir $@) Interface -.PHONY: all -all: build +.PHONY: install-crds +install-crds: generate ## Install CRDs into the K8s cluster specified in ~/.kube/config. + kubectl kustomize config/crd | kubectl apply -f - -##@ General +.PHONY: helmify +helm: manifests kustomize helmify + $(KUSTOMIZE) build config/default | $(HELMIFY) -crd-dir charts/kvm-node-agent -# 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 +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 -.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) +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 -##@ Development +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 -.PHONY: manifests -manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. - $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases +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 -.PHONY: generate -generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. - $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." +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 -# Generate mock files for all interfaces -INTERFACES = $(patsubst %.go,%_mock.go,$(wildcard internal/**/interface.go)) -mocks: moq $(INTERFACES) ## Generate mocks for interfaces. -.PHONY: FORCE -FORCE: -%_mock.go: %.go FORCE ## Generate interface mocks with https://github.com/matryer/moq - $(MOQ) -rm -out $@ $(dir $@) Interface - -.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 - -.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: mocks 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 kvm-node-agent-builder - $(CONTAINER_TOOL) buildx use kvm-node-agent-builder - - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross . - - $(CONTAINER_TOOL) buildx rm kvm-node-agent-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 -endif +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 -.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-$(KUSTOMIZE_VERSION) -CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen-$(CONTROLLER_TOOLS_VERSION) -ENVTEST ?= $(LOCALBIN)/setup-envtest-$(ENVTEST_VERSION) -GOLANGCI_LINT = $(LOCALBIN)/golangci-lint-$(GOLANGCI_LINT_VERSION) -MOQ = $(LOCALBIN)/moq-$(MOQ_VERSION) -HELMIFY ?= $(LOCALBIN)/helmify-$(HELMIFY_VERSION) - -## Tool Versions -KUSTOMIZE_VERSION ?= v5.6.0 -CONTROLLER_TOOLS_VERSION ?= v0.17.2 -ENVTEST_VERSION ?= release-0.18 -GOLANGCI_LINT_VERSION ?= v1.64.5 -MOQ_VERSION ?= v0.6.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/cmd/golangci-lint,${GOLANGCI_LINT_VERSION}) - -.PHONY: moq -moq: $(MOQ) ## Download moq locally if necessary. -$(MOQ): $(LOCALBIN) - $(call go-install-tool,$(MOQ),github.com/matryer/moq,$(MOQ_VERSION)) +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 -.PHONY: helmify -helmify: $(HELMIFY) ## Download helmify locally if necessary. -$(HELMIFY): $(LOCALBIN) - $(call go-install-tool,$(HELMIFY),github.com/arttor/helmify/cmd/helmify,$(HELMIFY_VERSION)) +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 -helm: manifests kustomize helmify - $(KUSTOMIZE) build config/default | $(HELMIFY) -crd-dir charts/kvm-node-agent +# 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 += + +# These definitions are overridable, e.g. to provide fixed version/commit values when +# no .git directory is present or to provide a fixed build date for reproducibility. +BININFO_VERSION ?= $(shell git describe --tags --always --abbrev=7) +BININFO_COMMIT_HASH ?= $(shell git rev-parse --verify HEAD) +BININFO_BUILD_DATE ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ") + +build-all: build/manager + +build/manager: FORCE generate + env $(GO_BUILDENV) go build $(GO_BUILDFLAGS) -ldflags '-s -w -X github.com/sapcc/go-api-declarations/bininfo.binName=manager -X github.com/sapcc/go-api-declarations/bininfo.version=$(BININFO_VERSION) -X github.com/sapcc/go-api-declarations/bininfo.commit=$(BININFO_COMMIT_HASH) -X github.com/sapcc/go-api-declarations/bininfo.buildDate=$(BININFO_BUILD_DATE) $(GO_LDFLAGS)' -o build/manager ./cmd -.PHONY: build-helm -build-helm: helm - mkdir -p dist - helm package charts/kvm-node-agent -d dist/ - -helm-push: build-helm - helm push dist/kvm-node-agent* ${OCI} - -# 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 (ideally with version) -# $2 - package url which can be installed -# $3 - specific version of package -define go-install-tool -@[ -f $(1) ] || { \ -set -e; \ -package=$(2)@$(3) ;\ -echo "Downloading $${package}" ;\ -GOBIN=$(LOCALBIN) go install $${package} ;\ -mv "$$(echo "$(1)" | sed "s/-$(3)$$//")" $(1) ;\ -} -endef +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="./..." + +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 -X github.com/sapcc/go-api-declarations/bininfo.binName=kvm-node-agent -X github.com/sapcc/go-api-declarations/bininfo.version=$(BININFO_VERSION) -X github.com/sapcc/go-api-declarations/bininfo.commit=$(BININFO_COMMIT_HASH) -X github.com/sapcc/go-api-declarations/bininfo.buildDate=$(BININFO_BUILD_DATE) $(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/kvm-node-agent\e[0m\n" + @goimports -w -local github.com/cobaltcore-dev/kvm-node-agent $(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 "BININFO_BUILD_DATE=$(BININFO_BUILD_DATE)\n" + @printf "BININFO_COMMIT_HASH=$(BININFO_COMMIT_HASH)\n" + @printf "BININFO_VERSION=$(BININFO_VERSION)\n" + @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 0000000..b5cd199 --- /dev/null +++ b/Makefile.maker.yaml @@ -0,0 +1,77 @@ +# 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: true + extraDirectives: + - "# Hardcoded to kvm-node-agent user/group on host" + - USER 42438:42438 + +golang: + autoupdateableDeps: ^github.com/(?:sapcc|sap-cloud-infrastructure)/ + 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/kvm-node-agent + +renovate: + enabled: true + assignees: + - notandy + - fwiesel + - mchristianl + - toanju + +reuse: + annotations: + - paths: + - PROJECT + - config/** + - charts/** + - internal/**/*_mock.go + - build/** + SPDX-FileCopyrightText: 'SAP SE or an SAP affiliate company and cobaltcore-dev contributors' + SPDX-License-Identifier: Apache-2.0 + +verbatim: | + # Generate mock files for all interfaces + INTERFACES = $(patsubst %.go,%_mock.go,$(wildcard internal/**/interface.go)) + mocks: $(INTERFACES) ## Generate mocks for interfaces. + .PHONY: FORCE + FORCE: + %_mock.go: %.go FORCE ## Generate interface mocks with https://github.com/matryer/moq + moq -rm -out $@ $(dir $@) Interface + + .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/kvm-node-agent diff --git a/REUSE.toml b/REUSE.toml index 91d939b..e919a56 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -1,37 +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 = "kvm-node-agent" +SPDX-PackageDownloadLocation = "https://github.com/cobaltcore-dev/kvm-node-agent" [[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", - "dist/**" + "PROJECT", + "config/**", + "charts/**", + "internal/**/*_mock.go", + "build/**", ] -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/api/v1alpha1/migration_types.go b/api/v1alpha1/migration_types.go index 8514f87..deb27df 100644 --- a/api/v1alpha1/migration_types.go +++ b/api/v1alpha1/migration_types.go @@ -33,7 +33,7 @@ type MigrationStatus struct { Origin string `json:"origin,omitempty"` Destination string `json:"destination,omitempty"` Type string `json:"type,omitempty"` - Started metav1.Time `json:"started,omitempty"` + Started metav1.Time `json:"started"` ErrMsg string `json:"errMsg,omitempty"` AutoConvergeThrottle string `json:"autoConvergeThrottle,omitempty"` DiskBps string `json:"diskBps,omitempty"` @@ -79,10 +79,10 @@ type MigrationStatus struct { // Migration is the Schema for the migrations API. type Migration struct { metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.ObjectMeta `json:"metadata"` - Spec MigrationSpec `json:"spec,omitempty"` - Status MigrationStatus `json:"status,omitempty"` + Spec MigrationSpec `json:"spec"` + Status MigrationStatus `json:"status"` } // +kubebuilder:object:root=true @@ -90,7 +90,7 @@ type Migration struct { // MigrationList contains a list of Migration. type MigrationList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` + metav1.ListMeta `json:"metadata"` Items []Migration `json:"items"` } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 2d79fe4..b2e07f5 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ //go:build !ignore_autogenerated /* -SPDX-FileCopyrightText: Copyright 2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors +SPDX-FileCopyrightText: Copyright 2025 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"); diff --git a/cmd/main.go b/cmd/main.go index 805c2fe..f64dae9 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -37,6 +37,7 @@ import ( _ "k8s.io/client-go/plugin/pkg/client/auth" certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + "github.com/sapcc/go-api-declarations/bininfo" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" @@ -86,12 +87,18 @@ func main() { "If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.") flag.BoolVar(&enableHTTP2, "enable-http2", false, "If set, HTTP/2 will be enabled for the metrics and webhook servers") + versionFlag := flag.Bool("version", false, "Print application version") opts := zap.Options{ Development: true, } opts.BindFlags(flag.CommandLine) flag.Parse() + if *versionFlag { + fmt.Printf("%s version %s (%s)\n", bininfo.Component(), bininfo.Version(), bininfo.BuildDate()) + os.Exit(0) + } + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) // if the enable-http2 flag is false (the default), http/2 should be disabled @@ -159,13 +166,13 @@ func main() { Cache: cache.Options{ ByObject: map[client.Object]cache.ByObject{ &corev1.Node{}: { - Field: fields.ParseSelectorOrDie(fmt.Sprintf("metadata.name=%s", sys.Hostname)), + Field: fields.ParseSelectorOrDie("metadata.name=" + sys.Hostname), }, &kvmv1.Hypervisor{}: { - Field: fields.ParseSelectorOrDie(fmt.Sprintf("metadata.name=%s", sys.Hostname)), + Field: fields.ParseSelectorOrDie("metadata.name=" + sys.Hostname), }, &corev1.Secret{}: { - Field: fields.ParseSelectorOrDie(fmt.Sprintf("metadata.name=%s", secretName)), + Field: fields.ParseSelectorOrDie("metadata.name=" + secretName), }, }, }, diff --git a/config/crd/bases/kvm.cloud.sap_migrations.yaml b/config/crd/bases/kvm.cloud.sap_migrations.yaml index fc3f6eb..8238e2e 100644 --- a/config/crd/bases/kvm.cloud.sap_migrations.yaml +++ b/config/crd/bases/kvm.cloud.sap_migrations.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.19.0 name: migrations.kvm.cloud.sap spec: group: kvm.cloud.sap @@ -142,7 +142,13 @@ spec: type: string type: type: string + required: + - started type: object + required: + - metadata + - spec + - status type: object served: true storage: true diff --git a/go.mod b/go.mod index 44869e2..67111fd 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/godbus/dbus/v5 v5.1.0 github.com/onsi/ginkgo/v2 v2.26.0 github.com/onsi/gomega v1.38.2 + github.com/sapcc/go-api-declarations v1.17.4 k8s.io/api v0.34.1 k8s.io/apimachinery v0.34.1 k8s.io/client-go v0.34.1 diff --git a/go.sum b/go.sum index 5cadb74..bce9a52 100644 --- a/go.sum +++ b/go.sum @@ -157,6 +157,8 @@ github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUO 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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sapcc/go-api-declarations v1.17.4 h1:F4smuE9x1NJ/7NAdytJ1wekeXT3QeRaYu3L/HyWKqqo= +github.com/sapcc/go-api-declarations v1.17.4/go.mod h1:MWmLjmvjftgyAugNUfIhsDsHIzXH1pn32cWLZpiluKg= github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index a2e2363..928409c 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,5 +1,5 @@ /* -SPDX-FileCopyrightText: Copyright 2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors +SPDX-FileCopyrightText: Copyright 2025 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"); diff --git a/internal/certificates/manage_libvirt.go b/internal/certificates/manage_libvirt.go index 0bf6056..7ba99fc 100644 --- a/internal/certificates/manage_libvirt.go +++ b/internal/certificates/manage_libvirt.go @@ -36,10 +36,8 @@ import ( "github.com/cobaltcore-dev/kvm-node-agent/internal/sys" ) -func GetSecretAndCertName(host string) (string, string) { - certName := fmt.Sprintf("libvirt-%s", host) - secretName := fmt.Sprintf("tls-%s", certName) - return secretName, certName +func GetSecretAndCertName(host string) (secretName, certName string) { + return "tls-" + certName, "libvirt-" + host } var ( diff --git a/internal/controller/hypervisor_controller.go b/internal/controller/hypervisor_controller.go index 5bf0950..e55943a 100644 --- a/internal/controller/hypervisor_controller.go +++ b/internal/controller/hypervisor_controller.go @@ -181,7 +181,9 @@ func (r *HypervisorReconciler) Reconcile(ctx context.Context, req ctrl.Request) // Update hypervisor instances hypervisor.Status.NumInstances = r.Libvirt.GetNumInstances() - hypervisor.Status.Instances, _ = r.Libvirt.GetInstances() + if hypervisor.Status.Instances, err = r.Libvirt.GetInstances(); err != nil { + log.Error(err, "failed to get instances") + } // Update capabilities status. if capabilities, err := r.Libvirt.GetCapabilities(); err == nil { @@ -208,7 +210,6 @@ func (r *HypervisorReconciler) Reconcile(ctx context.Context, req ctrl.Request) hypervisor.Spec.OperatingSystemVersion != hypervisor.Status.OperatingSystem.Version && // only update if the version is different to the installed version hypervisor.Spec.OperatingSystemVersion != hypervisor.Status.Update.Installed { - if hypervisor.Status.Update.Retry == 0 { // we reached the retry limit, unset the version to stop the update // failed message of past retries is still available in the conditions diff --git a/internal/controller/hypervisor_controller_test.go b/internal/controller/hypervisor_controller_test.go index cdcd48c..a427efb 100644 --- a/internal/controller/hypervisor_controller_test.go +++ b/internal/controller/hypervisor_controller_test.go @@ -20,21 +20,20 @@ package controller import ( "context" + kvmv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1" "github.com/coreos/go-systemd/v22/dbus" golibvirt "github.com/digitalocean/go-libvirt" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/reconcile" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/cobaltcore-dev/kvm-node-agent/internal/libvirt" "github.com/cobaltcore-dev/kvm-node-agent/internal/sys" "github.com/cobaltcore-dev/kvm-node-agent/internal/systemd" - kvmv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1" ) var _ = Describe("Hypervisor Controller", func() { diff --git a/internal/controller/secret_controller.go b/internal/controller/secret_controller.go index 8358bb1..9b1dc32 100644 --- a/internal/controller/secret_controller.go +++ b/internal/controller/secret_controller.go @@ -81,21 +81,27 @@ func (r *SecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr return ctrl.Result{}, nil } - _ = r.setTLSStatusCondition(ctx, metav1.ConditionFalse, - "Installing", "Installing TLS certificate from Secret") + if err = r.setTLSStatusCondition(ctx, metav1.ConditionFalse, + "Installing", "Installing TLS certificate from Secret"); err != nil { + return ctrl.Result{}, err + } if err = certificates.UpdateTLSCertificate(ctx, secret.Data); err != nil { // update conditions - _ = r.setTLSStatusCondition(ctx, metav1.ConditionFalse, - "FailedToUpdateTLSCertificate", fmt.Sprintf("Failed to update TLS certificate: %v", err)) + if err := r.setTLSStatusCondition(ctx, metav1.ConditionFalse, + "FailedToUpdateTLSCertificate", fmt.Sprintf("Failed to update TLS certificate: %v", err)); err != nil { + return ctrl.Result{}, err + } return ctrl.Result{}, err } // Reload the libvirtd service if _, err = r.Systemd.StartUnit(ctx, "virt-admin-server-update-tls.service"); err != nil { - _ = r.setTLSStatusCondition(ctx, metav1.ConditionFalse, + if err := r.setTLSStatusCondition(ctx, metav1.ConditionFalse, "FailedToStartUpdateTLSService", - fmt.Sprintf("Failed to start virt-admin-server-update-tls service: %v", err)) + fmt.Sprintf("Failed to start virt-admin-server-update-tls service: %v", err)); err != nil { + return ctrl.Result{}, err + } log.Error(err, "failed to start virt-admin-server-update-tls service") // Start the libvirtd service if _, err = r.Systemd.StartUnit(ctx, "libvirtd.service"); err != nil { @@ -112,10 +118,8 @@ func (r *SecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr } r.lastResourceVersion = secret.ResourceVersion - _ = r.setTLSStatusCondition(ctx, metav1.ConditionTrue, "Ready", + return ctrl.Result{}, r.setTLSStatusCondition(ctx, metav1.ConditionTrue, "Ready", "TLS certificate is ready and updated") - - return ctrl.Result{}, nil } // SetupWithManager sets up the controller with the Manager. @@ -149,6 +153,7 @@ func (r *SecretReconciler) SetupWithManager(mgr ctrl.Manager) error { func (r *SecretReconciler) setTLSStatusCondition(ctx context.Context, status metav1.ConditionStatus, reason, message string) error { + log := logger.FromContext(ctx) hv := &kvmv1.Hypervisor{} diff --git a/internal/emulator/systemd.go b/internal/emulator/systemd.go index 0d73f89..dc91a81 100644 --- a/internal/emulator/systemd.go +++ b/internal/emulator/systemd.go @@ -34,7 +34,7 @@ func NewSystemdEmulator(ctx context.Context) *systemd.InterfaceMock { log.Info("CloseFunc called") }, GetUnitByNameFunc: func(ctx context.Context, unit string) (dbus.UnitStatus, error) { - log.Info("GetUnitByNameFunc called with with unit = " + unit) + log.Info("GetUnitByNameFunc called with unit = " + unit) return dbus.UnitStatus{}, nil }, IsConnectedFunc: func() bool { diff --git a/internal/evacuation/eviction.go b/internal/evacuation/eviction.go index fdc0c74..75d04f3 100644 --- a/internal/evacuation/eviction.go +++ b/internal/evacuation/eviction.go @@ -54,8 +54,8 @@ func (e *EvictionController) EvictCurrentHost(ctx context.Context) error { } u := &unstructured.Unstructured{} - u.SetUnstructuredContent(map[string]interface{}{ - "spec": map[string]interface{}{ + u.SetUnstructuredContent(map[string]any{ + "spec": map[string]any{ "hypervisor": sys.Hostname, "reason": "kvm-node-agent: emergency evacuation due to host reboot", }, diff --git a/internal/evacuation/eviction_test.go b/internal/evacuation/eviction_test.go index 4c8a73e..4c48e4a 100644 --- a/internal/evacuation/eviction_test.go +++ b/internal/evacuation/eviction_test.go @@ -74,8 +74,8 @@ var _ = Describe("Evacuation Callback", func() { Expect(err).NotTo(HaveOccurred()) u := &unstructured.Unstructured{} - u.SetUnstructuredContent(map[string]interface{}{ - "spec": map[string]interface{}{ + u.SetUnstructuredContent(map[string]any{ + "spec": map[string]any{ "hypervisor": sys.Hostname, "reason": "kvm-node-agent: emergency evacuation due to host reboot", }, diff --git a/internal/libvirt/libvirt_events.go b/internal/libvirt/libvirt_events.go index 8e1fe6f..806c737 100644 --- a/internal/libvirt/libvirt_events.go +++ b/internal/libvirt/libvirt_events.go @@ -87,12 +87,12 @@ func (l *LibVirt) runMigrationListener(ctx context.Context) { e := event.(*libvirt.DomainEventCallbackMigrationIterationMsg) domain := e.Dom uuid := GetOpenstackUUID(domain) - log := log.WithValues("server", uuid) - log.Info("migration iteration", "iteration", e.Iteration) + serverLog := log.WithValues("server", uuid) + serverLog.Info("migration iteration", "iteration", e.Iteration) // migration started if err = l.startMigrationWatch(ctx, domain); err != nil { - log.Error(err, "failed to starting migration watch") + serverLog.Error(err, "failed to starting migration watch") } case event := <-jobCompletedEvents: @@ -103,24 +103,24 @@ func (l *LibVirt) runMigrationListener(ctx context.Context) { case event := <-lifecycleEvents: e := event.(*libvirt.DomainEventCallbackLifecycleMsg) domain := e.Msg.Dom - log := log.WithValues("server", GetOpenstackUUID(domain)) + serverLog := log.WithValues("server", GetOpenstackUUID(domain)) switch e.Msg.Event { case int32(libvirt.DomainEventDefined): switch e.Msg.Detail { case int32(libvirt.DomainEventDefinedAdded): - log.Info("domain added") + serverLog.Info("domain added") // add domain to the list of inactive domains l.domains[libvirt.ConnectListDomainsInactive] = append(l.domains[libvirt.ConnectListDomainsInactive], domain) case int32(libvirt.DomainEventDefinedUpdated): - log.Info("domain updated") + serverLog.Info("domain updated") case int32(libvirt.DomainEventDefinedRenamed): - log.Info("domain renamed") + serverLog.Info("domain renamed") case int32(libvirt.DomainEventDefinedFromSnapshot): - log.Info("domain defined from snapshot") + serverLog.Info("domain defined from snapshot") } case int32(libvirt.DomainEventUndefined): - log.Info("domain undefined") + serverLog.Info("domain undefined") // remove domain from the list of inactive domains for i, d := range l.domains[libvirt.ConnectListDomainsInactive] { if d.Name == domain.Name { @@ -135,26 +135,26 @@ func (l *LibVirt) runMigrationListener(ctx context.Context) { l.domains[libvirt.ConnectListDomainsActive] = append(l.domains[libvirt.ConnectListDomainsActive], domain) switch e.Msg.Detail { case int32(libvirt.DomainEventStartedBooted): - log.Info("domain booted") + serverLog.Info("domain booted") case int32(libvirt.DomainEventStartedMigrated): - log.Info("incoming migration started") + serverLog.Info("incoming migration started") case int32(libvirt.DomainEventStartedRestored): - log.Info("domain restored") + serverLog.Info("domain restored") case int32(libvirt.DomainEventStartedFromSnapshot): - log.Info("domain started from snapshot") + serverLog.Info("domain started from snapshot") case int32(libvirt.DomainEventStartedWakeup): - log.Info("domain woken up") + serverLog.Info("domain woken up") } case int32(libvirt.DomainEventSuspended): - log.Info("domain suspended") + serverLog.Info("domain suspended") case int32(libvirt.DomainEventResumed): - log.Info("domain resumed") + serverLog.Info("domain resumed") // incoming migration completed, finalize migration status if err = l.patchMigration(ctx, domain, true); client.IgnoreNotFound(err) != nil { - log.Error(err, "failed to update migration status") + serverLog.Error(err, "failed to update migration status") } case int32(libvirt.DomainEventStopped): - log.Info("domain stopped") + serverLog.Info("domain stopped") // remove domain from the list of active domains for i, d := range l.domains[libvirt.ConnectListDomainsActive] { @@ -167,17 +167,19 @@ func (l *LibVirt) runMigrationListener(ctx context.Context) { } l.stopMigrationWatch(ctx, domain) case int32(libvirt.DomainEventShutdown): - log.Info("domain shutdown") + serverLog.Info("domain shutdown") l.stopMigrationWatch(ctx, domain) case int32(libvirt.DomainEventPmsuspended): - log.Info("domain PM suspended") + serverLog.Info("domain PM suspended") case int32(libvirt.DomainEventCrashed): - log.Info("domain crashed") + serverLog.Info("domain crashed") } case <-ctx.Done(): log.Info("shutting down migration listener") - _ = l.virt.ConnectRegisterCloseCallback() + if err = l.virt.ConnectRegisterCloseCallback(); err != nil { + log.Error(err, "failed to unregister close callback") + } // read from events to drain the channel if _, ok := <-lifecycleEvents; !ok { @@ -190,7 +192,7 @@ func (l *LibVirt) runMigrationListener(ctx context.Context) { log.Info("job completed events drained") } - case <-l.virt.Disconnected(): //nolint:typecheck + case <-l.virt.Disconnected(): log.Info("libvirt disconnected, shutting down migration listener") // stopping all migration watches @@ -383,13 +385,13 @@ func (l *LibVirt) populateDomainJobInfo(domain libvirt.Domain, migration *v1alph migration.Status.Operation = "snapshot_delete" } case "time_elapsed": - migration.Status.TimeElapsed = time.Duration(param.Value.I.(uint64) * 1000 * 1000).String() + migration.Status.TimeElapsed = time.Duration(param.Value.I.(int64) * 1000 * 1000).String() case "time_remaining": migration.Status.TimeRemaining = time.Duration(param.Value.I.(uint32) * 1000 * 1000).String() case "downtime": - migration.Status.Downtime = time.Duration(param.Value.I.(uint64) * 1000 * 1000).String() + migration.Status.Downtime = time.Duration(param.Value.I.(int64) * 1000 * 1000).String() case "setup_time": - migration.Status.SetupTime = time.Duration(param.Value.I.(uint64) * 1000 * 1000).String() + migration.Status.SetupTime = time.Duration(param.Value.I.(int64) * 1000 * 1000).String() case "data_total": migration.Status.DataTotal = ByteCountIEC(param.Value.I.(uint64)) case "data_processed": @@ -433,7 +435,6 @@ func (l *LibVirt) populateDomainJobInfo(domain libvirt.Domain, migration *v1alph case "errmsg": migration.Status.ErrMsg = param.Value.I.(string) } - } return err } diff --git a/internal/libvirt/libvirt_status_thread.go b/internal/libvirt/libvirt_status_thread.go index 85a1584..5364ee3 100644 --- a/internal/libvirt/libvirt_status_thread.go +++ b/internal/libvirt/libvirt_status_thread.go @@ -50,7 +50,9 @@ func (l *LibVirt) runStatusThread(ctx context.Context) { log.Info("starting status thread") // run immediately, and every minute after - _ = l.updateDomains() + if err := l.updateDomains(); err != nil { + log.Error(err, "failed to update domains") + } for { select { diff --git a/internal/sys/release.go b/internal/sys/release.go deleted file mode 100644 index 57168ea..0000000 --- a/internal/sys/release.go +++ /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 - -Licensed under the Apache License, LibVirtVersion 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 sys - -import ( - "bufio" - "context" - "os" - - logger "sigs.k8s.io/controller-runtime/pkg/log" -) - -func GetOSVersion(ctx context.Context) string { - log := logger.FromContext(ctx, "os", "GetOSVersion") - - f, err := os.Open("/etc/os-release") - if err != nil { - log.Error(err, "failed to open /etc/os-release") - return "unknown" - } - defer func() { _ = f.Close() }() - - buf := bufio.NewScanner(f) - for buf.Scan() { - // skip comments - if len(buf.Text()) > 0 && buf.Text()[0] == '#' { - continue - } - - // check if line starts with IMAGE_VERSION - if len(buf.Text()) > 0 && buf.Text()[0] == 'I' { - if len(buf.Text()) > 13 && buf.Text()[:13] == "IMAGE_VERSION" { - return buf.Text()[14:] - } - } - } - return "unknown" -} diff --git a/internal/systemd/interface.go b/internal/systemd/interface.go index a894a21..74c04d6 100644 --- a/internal/systemd/interface.go +++ b/internal/systemd/interface.go @@ -27,33 +27,33 @@ import ( ) type Descriptor struct { - Hostname string `json:"Hostname,omitempty"` - StaticHostname string `json:"StaticHostname,omitempty"` - PrettyHostname interface{} `json:"PrettyHostname,omitempty"` - DefaultHostname string `json:"DefaultHostname,omitempty"` - HostnameSource string `json:"HostnameSource,omitempty"` - IconName string `json:"IconName,omitempty"` - Chassis string `json:"Chassis,omitempty"` - Deployment interface{} `json:"Deployment,omitempty"` - Location interface{} `json:"Location,omitempty"` - KernelName string `json:"KernelName,omitempty"` - KernelRelease string `json:"KernelRelease,omitempty"` - KernelVersion string `json:"KernelVersion,omitempty"` - OperatingSystemPrettyName string `json:"PrettyName,omitempty"` - OperatingSystemCPEName interface{} `json:"OperatingSystemCPEName,omitempty"` - OperatingSystemHomeURL string `json:"OperatingSystemHomeURL,omitempty"` - OperatingSystemReleaseData []string `json:"OperatingSystemReleaseData,omitempty"` - MachineInformationData []interface{} `json:"MachineInformationData,omitempty"` - HardwareVendor string `json:"HardwareVendor,omitempty"` - HardwareModel string `json:"HardwareModel,omitempty"` - HardwareSerial string `json:"HardwareSerial,omitempty"` - FirmwareVersion string `json:"FirmwareVersion,omitempty"` - FirmwareVendor string `json:"FirmwareVendor,omitempty"` - FirmwareDate int64 `json:"FirmwareDate,omitempty"` - MachineID string `json:"MachineID,omitempty"` - BootID string `json:"BootID,omitempty"` - ProductUUID string `json:"ProductUUID,omitempty"` - VSockCID interface{} `json:"VSockCID,omitempty"` + Hostname string `json:"Hostname,omitempty"` + StaticHostname string `json:"StaticHostname,omitempty"` + PrettyHostname any `json:"PrettyHostname,omitempty"` + DefaultHostname string `json:"DefaultHostname,omitempty"` + HostnameSource string `json:"HostnameSource,omitempty"` + IconName string `json:"IconName,omitempty"` + Chassis string `json:"Chassis,omitempty"` + Deployment any `json:"Deployment,omitempty"` + Location any `json:"Location,omitempty"` + KernelName string `json:"KernelName,omitempty"` + KernelRelease string `json:"KernelRelease,omitempty"` + KernelVersion string `json:"KernelVersion,omitempty"` + OperatingSystemPrettyName string `json:"PrettyName,omitempty"` + OperatingSystemCPEName any `json:"OperatingSystemCPEName,omitempty"` + OperatingSystemHomeURL string `json:"OperatingSystemHomeURL,omitempty"` + OperatingSystemReleaseData []string `json:"OperatingSystemReleaseData,omitempty"` + MachineInformationData []any `json:"MachineInformationData,omitempty"` + HardwareVendor string `json:"HardwareVendor,omitempty"` + HardwareModel string `json:"HardwareModel,omitempty"` + HardwareSerial string `json:"HardwareSerial,omitempty"` + FirmwareVersion string `json:"FirmwareVersion,omitempty"` + FirmwareVendor string `json:"FirmwareVendor,omitempty"` + FirmwareDate int64 `json:"FirmwareDate,omitempty"` + MachineID string `json:"MachineID,omitempty"` + BootID string `json:"BootID,omitempty"` + ProductUUID string `json:"ProductUUID,omitempty"` + VSockCID any `json:"VSockCID,omitempty"` } type Interface interface { diff --git a/internal/systemd/systemd.go b/internal/systemd/systemd.go index d47c0c5..ea47b9f 100644 --- a/internal/systemd/systemd.go +++ b/internal/systemd/systemd.go @@ -116,7 +116,7 @@ func NewSystemd(ctx context.Context) (*SystemdConn, error) { // and registers a shutdown callback func (s *SystemdConn) EnableShutdownInhibit(ctx context.Context, cb func(context.Context) error) error { if s.fd != -1 { - return fmt.Errorf("shutdown inhibition already enabled") + return errors.New("shutdown inhibition already enabled") } log := logger.Log.WithName("systemd") @@ -238,7 +238,6 @@ func (s *SystemdConn) GetUnitByName(ctx context.Context, unit string) (systemd.U } func (s *SystemdConn) StartUnit(ctx context.Context, unit string) (int, error) { - return s.conn.StartUnitContext(ctx, unit, "replace", nil) } @@ -255,7 +254,7 @@ func (s *SystemdConn) ReconcileSysUpdate(ctx context.Context, hv *v1.Hypervisor) // Needs to be connected to systemd if !s.IsConnected() { - return false, fmt.Errorf("not connected to systemd") + return false, errors.New("not connected to systemd") } unit := fmt.Sprintf("systemd-sysupdate@%s.service", version) diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..8178dbe --- /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 + ]; +}