From c9d4fe58b9666849db27b7267da51e6d48c5cd65 Mon Sep 17 00:00:00 2001 From: Utku Ozdemir Date: Mon, 14 Oct 2024 11:45:28 +0200 Subject: [PATCH] feat: implement Talos agent Add initial implementation of the Talos agent mode service. Related to siderolabs/omni#660. Signed-off-by: Utku Ozdemir --- .codecov.yml | 18 + .conform.yaml | 48 ++ .dockerignore | 13 + .github/workflows/ci.yaml | 126 ++++ .github/workflows/slack-notify.yaml | 92 +++ .gitignore | 5 + .golangci.yml | 150 +++++ .kres.yaml | 60 ++ .license-header.go.txt | 3 + .markdownlint.json | 9 + Dockerfile | 190 ++++++ LICENSE | 373 +++++++++++ Makefile | 227 +++++++ README.md | 5 + api/provider/provider.pb.go | 378 +++++++++++ api/provider/provider.proto | 35 + api/provider/provider_grpc.pb.go | 198 ++++++ api/provider/provider_vtproto.pb.go | 919 ++++++++++++++++++++++++++ api/specs/specs.pb.go | 157 +++++ api/specs/specs.proto | 14 + api/specs/specs_vtproto.pb.go | 273 ++++++++ cmd/provider/main.go | 162 +++++ go.mod | 85 +++ go.sum | 343 ++++++++++ hack/release.sh | 149 +++++ hack/release.toml | 11 + internal/agent/controller.go | 66 ++ internal/config/config.go | 86 +++ internal/constants/constants.go | 14 + internal/debug/debug.go | 6 + internal/debug/disabled.go | 10 + internal/debug/enabled.go | 10 + internal/dhcp/dhcp.go | 6 + internal/dhcp/proxy.go | 240 +++++++ internal/imagefactory/imagefactory.go | 128 ++++ internal/ip/ip.go | 51 ++ internal/ipxe/ipxe.go | 137 ++++ internal/ipxe/patch.go | 205 ++++++ internal/meta/meta.go | 9 + internal/omni/omni.go | 188 ++++++ internal/provider/data/icon.svg | 1 + internal/provider/provider.go | 135 ++++ internal/resources/machine.go | 49 ++ internal/server/server.go | 132 ++++ internal/service/service.go | 144 ++++ internal/tftp/tftp_server.go | 124 ++++ internal/version/data/sha | 1 + internal/version/data/tag | 1 + internal/version/version.go | 41 ++ 49 files changed, 5827 insertions(+) create mode 100644 .codecov.yml create mode 100644 .conform.yaml create mode 100644 .dockerignore create mode 100644 .github/workflows/ci.yaml create mode 100644 .github/workflows/slack-notify.yaml create mode 100644 .gitignore create mode 100644 .golangci.yml create mode 100644 .kres.yaml create mode 100644 .license-header.go.txt create mode 100644 .markdownlint.json create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 api/provider/provider.pb.go create mode 100644 api/provider/provider.proto create mode 100644 api/provider/provider_grpc.pb.go create mode 100644 api/provider/provider_vtproto.pb.go create mode 100644 api/specs/specs.pb.go create mode 100644 api/specs/specs.proto create mode 100644 api/specs/specs_vtproto.pb.go create mode 100644 cmd/provider/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100755 hack/release.sh create mode 100644 hack/release.toml create mode 100644 internal/agent/controller.go create mode 100644 internal/config/config.go create mode 100644 internal/constants/constants.go create mode 100644 internal/debug/debug.go create mode 100644 internal/debug/disabled.go create mode 100644 internal/debug/enabled.go create mode 100644 internal/dhcp/dhcp.go create mode 100644 internal/dhcp/proxy.go create mode 100644 internal/imagefactory/imagefactory.go create mode 100644 internal/ip/ip.go create mode 100644 internal/ipxe/ipxe.go create mode 100644 internal/ipxe/patch.go create mode 100644 internal/meta/meta.go create mode 100644 internal/omni/omni.go create mode 100644 internal/provider/data/icon.svg create mode 100644 internal/provider/provider.go create mode 100644 internal/resources/machine.go create mode 100644 internal/server/server.go create mode 100644 internal/service/service.go create mode 100644 internal/tftp/tftp_server.go create mode 100644 internal/version/data/sha create mode 100644 internal/version/data/tag create mode 100644 internal/version/version.go diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..805d898 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,18 @@ +# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. +# +# Generated on 2024-10-14T09:32:55Z by kres 34e72ac. + +codecov: + require_ci_to_pass: false + +coverage: + status: + project: + default: + target: 0% + threshold: 0.5% + base: auto + if_ci_failed: success + patch: off + +comment: false diff --git a/.conform.yaml b/.conform.yaml new file mode 100644 index 0000000..333383f --- /dev/null +++ b/.conform.yaml @@ -0,0 +1,48 @@ +# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. +# +# Generated on 2024-10-14T09:32:55Z by kres 34e72ac. + +policies: + - type: commit + spec: + dco: true + gpg: + required: true + identity: + gitHubOrganization: siderolabs + spellcheck: + locale: US + maximumOfOneCommit: true + header: + length: 89 + imperative: true + case: lower + invalidLastCharacters: . + body: + required: true + conventional: + types: + - chore + - docs + - perf + - refactor + - style + - test + - release + scopes: + - .* + - type: license + spec: + root: . + skipPaths: + - .git/ + - testdata/ + includeSuffixes: + - .go + excludeSuffixes: + - .pb.go + - .pb.gw.go + header: | + // This Source Code Form is subject to the terms of the Mozilla Public + // License, v. 2.0. If a copy of the MPL was not distributed with this + // file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0523122 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. +# +# Generated on 2024-10-14T09:34:03Z by kres 34e72ac. + +* +!api +!cmd +!internal +!go.mod +!go.sum +!.golangci.yml +!README.md +!.markdownlint.json diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..8465674 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,126 @@ +# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. +# +# Generated on 2024-10-18T21:14:30Z by kres 34e72ac. + +name: default +concurrency: + group: ${{ github.head_ref || github.run_id }} + cancel-in-progress: true +"on": + push: + branches: + - main + - release-* + tags: + - v* + pull_request: + branches: + - main + - release-* +jobs: + default: + permissions: + actions: read + contents: write + issues: read + packages: write + pull-requests: read + runs-on: + - self-hosted + - generic + if: (!startsWith(github.head_ref, 'renovate/') && !startsWith(github.head_ref, 'dependabot/')) + steps: + - name: gather-system-info + id: system-info + uses: kenchan0130/actions-system-info@v1.3.0 + continue-on-error: true + - name: print-system-info + run: | + MEMORY_GB=$((${{ steps.system-info.outputs.totalmem }}/1024/1024/1024)) + + OUTPUTS=( + "CPU Core: ${{ steps.system-info.outputs.cpu-core }}" + "CPU Model: ${{ steps.system-info.outputs.cpu-model }}" + "Hostname: ${{ steps.system-info.outputs.hostname }}" + "NodeName: ${NODE_NAME}" + "Kernel release: ${{ steps.system-info.outputs.kernel-release }}" + "Kernel version: ${{ steps.system-info.outputs.kernel-version }}" + "Name: ${{ steps.system-info.outputs.name }}" + "Platform: ${{ steps.system-info.outputs.platform }}" + "Release: ${{ steps.system-info.outputs.release }}" + "Total memory: ${MEMORY_GB} GB" + ) + + for OUTPUT in "${OUTPUTS[@]}";do + echo "${OUTPUT}" + done + continue-on-error: true + - name: checkout + uses: actions/checkout@v4 + - name: Unshallow + run: | + git fetch --prune --unshallow + - name: Set up Docker Buildx + id: setup-buildx + uses: docker/setup-buildx-action@v3 + with: + driver: remote + endpoint: tcp://buildkit-amd64.ci.svc.cluster.local:1234 + timeout-minutes: 10 + - name: base + run: | + make base + - name: unit-tests + run: | + make unit-tests + - name: unit-tests-race + run: | + make unit-tests-race + - name: coverage + uses: codecov/codecov-action@v4 + with: + files: _out/coverage-unit-tests.txt + token: ${{ secrets.CODECOV_TOKEN }} + timeout-minutes: 3 + - name: provider + run: | + make provider + - name: lint + run: | + make lint + - name: Login to registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + password: ${{ secrets.GITHUB_TOKEN }} + registry: ghcr.io + username: ${{ github.repository_owner }} + - name: image-provider + run: | + make image-provider + - name: push-provider + if: github.event_name != 'pull_request' + env: + PLATFORM: linux/amd64,linux/arm64 + PUSH: "true" + run: | + make image-provider + - name: Generate Checksums + if: startsWith(github.ref, 'refs/tags/') + run: | + cd _out + sha256sum provider-* > sha256sum.txt + sha512sum provider-* > sha512sum.txt + - name: release-notes + if: startsWith(github.ref, 'refs/tags/') + run: | + make release-notes + - name: Release + if: startsWith(github.ref, 'refs/tags/') + uses: crazy-max/ghaction-github-release@v2 + with: + body_path: _out/RELEASE_NOTES.md + draft: "true" + files: |- + _out/provider-* + _out/sha*.txt diff --git a/.github/workflows/slack-notify.yaml b/.github/workflows/slack-notify.yaml new file mode 100644 index 0000000..22319fe --- /dev/null +++ b/.github/workflows/slack-notify.yaml @@ -0,0 +1,92 @@ +# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. +# +# Generated on 2024-10-14T09:32:55Z by kres 34e72ac. + +name: slack-notify +"on": + workflow_run: + workflows: + - default + types: + - completed +jobs: + slack-notify: + runs-on: + - self-hosted + - generic + if: github.event.workflow_run.conclusion != 'skipped' + steps: + - name: Get PR number + id: get-pr-number + if: github.event.workflow_run.event == 'pull_request' + env: + GH_TOKEN: ${{ github.token }} + run: | + echo pull_request_number=$(gh pr view -R ${{ github.repository }} ${{ github.event.workflow_run.head_repository.owner.login }}:${{ github.event.workflow_run.head_branch }} --json number --jq .number) >> $GITHUB_OUTPUT + - name: Slack Notify + uses: slackapi/slack-github-action@v1 + with: + channel-id: proj-talos-maintainers + payload: | + { + "attachments": [ + { + "color": "${{ github.event.workflow_run.conclusion == 'success' && '#2EB886' || github.event.workflow_run.conclusion == 'failure' && '#A30002' || '#FFCC00' }}", + "fallback": "test", + "blocks": [ + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "${{ github.event.workflow_run.event == 'pull_request' && format('*Pull Request:* {0} (`{1}`)\n<{2}/pull/{3}|{4}>', github.repository, github.ref_name, github.event.repository.html_url, steps.get-pr-number.outputs.pull_request_number, github.event.workflow_run.display_title) || format('*Build:* {0} (`{1}`)\n<{2}/commit/{3}|{4}>', github.repository, github.ref_name, github.event.repository.html_url, github.sha, github.event.workflow_run.display_title) }}" + }, + { + "type": "mrkdwn", + "text": "*Status:*\n`${{ github.event.workflow_run.conclusion }}`" + } + ] + }, + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "*Author:*\n`${{ github.actor }}`" + }, + { + "type": "mrkdwn", + "text": "*Event:*\n`${{ github.event.workflow_run.event }}`" + } + ] + }, + { + "type": "divider" + }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Logs" + }, + "url": "${{ github.event.workflow_run.html_url }}" + }, + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Commit" + }, + "url": "${{ github.event.repository.html_url }}/commit/${{ github.sha }}" + } + ] + } + ] + } + ] + } + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7c5e64a --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. +# +# Generated on 2024-10-14T09:32:55Z by kres 34e72ac. + +_out diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..200df9e --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,150 @@ +# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. +# +# Generated on 2024-10-14T09:32:55Z by kres 34e72ac. + +# options for analysis running +run: + timeout: 10m + issues-exit-code: 1 + tests: true + build-tags: [ ] + modules-download-mode: readonly + +# output configuration options +output: + formats: + - format: colored-line-number + path: stdout + print-issued-lines: true + print-linter-name: true + uniq-by-line: true + path-prefix: "" + +# all available settings of specific linters +linters-settings: + dogsled: + max-blank-identifiers: 2 + dupl: + threshold: 150 + errcheck: + check-type-assertions: true + check-blank: true + exhaustive: + default-signifies-exhaustive: false + gci: + sections: + - standard # Standard section: captures all standard packages. + - default # Default section: contains all imports that could not be matched to another section type. + - localmodule # Imports from the same module. + gocognit: + min-complexity: 30 + nestif: + min-complexity: 5 + goconst: + min-len: 3 + min-occurrences: 3 + gocritic: + disabled-checks: [ ] + gocyclo: + min-complexity: 20 + godot: + scope: declarations + gofmt: + simplify: true + gomodguard: { } + govet: + enable-all: true + lll: + line-length: 200 + tab-width: 4 + misspell: + locale: US + ignore-words: [ ] + nakedret: + max-func-lines: 30 + prealloc: + simple: true + range-loops: true # Report preallocation suggestions on range loops, true by default + for-loops: false # Report preallocation suggestions on for loops, false by default + nolintlint: + allow-unused: false + allow-no-explanation: [ ] + require-explanation: false + require-specific: true + rowserrcheck: { } + testpackage: { } + unparam: + check-exported: false + unused: + local-variables-are-used: false + whitespace: + multi-if: false # Enforces newlines (or comments) after every multi-line if statement + multi-func: false # Enforces newlines (or comments) after every multi-line function signature + wsl: + strict-append: true + allow-assign-and-call: true + allow-multiline-assign: true + allow-cuddle-declarations: false + allow-trailing-comment: false + force-case-trailing-whitespace: 0 + force-err-cuddling: false + allow-separated-leading-comment: false + gofumpt: + extra-rules: false + cyclop: + # the maximal code complexity to report + max-complexity: 20 + depguard: + rules: + prevent_unmaintained_packages: + list-mode: lax # allow unless explicitly denied + files: + - $all + deny: + - pkg: io/ioutil + desc: "replaced by io and os packages since Go 1.16: https://tip.golang.org/doc/go1.16#ioutil" + +linters: + enable-all: true + disable-all: false + fast: false + disable: + - exhaustruct + - err113 + - forbidigo + - funlen + - gochecknoglobals + - gochecknoinits + - godox + - gomnd + - gomoddirectives + - gosec + - inamedparam + - ireturn + - mnd + - nestif + - nonamedreturns + - paralleltest + - tagalign + - tagliatelle + - thelper + - varnamelen + - wrapcheck + - testifylint # complains about our assert recorder and has a number of false positives for assert.Greater(t, thing, 1) + - protogetter # complains about us using Value field on typed spec, instead of GetValue which has a different signature + - perfsprint # complains about us using fmt.Sprintf in non-performance critical code, updating just kres took too long + - goimports # same as gci + - musttag # seems to be broken - goes into imported libraries and reports issues there + +issues: + exclude: [ ] + exclude-rules: [ ] + exclude-use-default: false + exclude-case-sensitive: false + max-issues-per-linter: 10 + max-same-issues: 3 + new: false + +severity: + default-severity: error + case-sensitive: false diff --git a/.kres.yaml b/.kres.yaml new file mode 100644 index 0000000..115d0d3 --- /dev/null +++ b/.kres.yaml @@ -0,0 +1,60 @@ +kind: common.Image +name: image-metal-agent +spec: + pushLatest: false + extraEnvironment: + PLATFORM: linux/amd64,linux/arm64 +--- +kind: common.Image +name: image-provider +spec: + pushLatest: false + extraEnvironment: + PLATFORM: linux/amd64,linux/arm64 + copyFrom: + - stage: ghcr.io/siderolabs/musl:v1.8.0-16-g71d23b4 # required by zbin + source: / + destination: / + - stage: ghcr.io/siderolabs/liblzma:v1.8.0-16-g71d23b4 # required by zbin + source: / + destination: / + - stage: ghcr.io/siderolabs/openssl:v1.8.0-16-g71d23b4 # required by ipmitool + source: / + destination: / + - stage: ghcr.io/siderolabs/ipmitool:v1.8.0-16-g71d23b4 + source: / + destination: / + - stage: ghcr.io/siderolabs/ipxe:v1.8.0-16-g71d23b4 + source: /usr/libexec/zbin + destination: /bin/zbin + - stage: ghcr.io/siderolabs/ipxe:v1.8.0-16-g71d23b4@sha256:36f8b53a8bc50186207327f2fc46d383547473ea1b030d0474f43db3b5a29dd1 + source: /usr/libexec/ + destination: /var/lib/ipxe/amd64 + - stage: ghcr.io/siderolabs/ipxe:v1.8.0-16-g71d23b4@sha256:1a704193584f0d8147e47b0e0fb0d4cc8a1102e4ff22f68ae22ccdafd98863ae + source: /usr/libexec/ + destination: /var/lib/ipxe/arm64 +--- +kind: golang.Build +spec: + outputs: + linux-amd64: + GOOS: linux + GOARCH: amd64 + linux-arm64: + GOOS: linux + GOARCH: arm64 +--- +kind: golang.Generate +spec: + versionPackagePath: internal/version + baseSpecPath: /api + vtProtobufEnabled: true + specs: + - source: api/provider/provider.proto + subdirectory: provider + - source: api/specs/specs.proto + subdirectory: specs +--- +kind: service.CodeCov +spec: + targetThreshold: 0 diff --git a/.license-header.go.txt b/.license-header.go.txt new file mode 100644 index 0000000..66e0819 --- /dev/null +++ b/.license-header.go.txt @@ -0,0 +1,3 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..8631d63 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,9 @@ +# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. +# +# Generated on 2024-10-14T09:32:55Z by kres 34e72ac. + +{ + "MD013": false, + "MD033": false, + "default": true + } diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c6bd7b9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,190 @@ +# syntax = docker/dockerfile-upstream:1.10.0-labs + +# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. +# +# Generated on 2024-10-23T21:12:06Z by kres 6d3cad4. + +ARG TOOLCHAIN + +FROM ghcr.io/siderolabs/ca-certificates:v1.8.0 AS image-ca-certificates + +FROM ghcr.io/siderolabs/fhs:v1.8.0 AS image-fhs + +# runs markdownlint +FROM docker.io/oven/bun:1.1.32-alpine AS lint-markdown +WORKDIR /src +RUN bun i markdownlint-cli@0.42.0 sentences-per-line@0.2.1 +COPY .markdownlint.json . +COPY ./README.md ./README.md +RUN bunx markdownlint --ignore "CHANGELOG.md" --ignore "**/node_modules/**" --ignore '**/hack/chglog/**' --rules node_modules/sentences-per-line/index.js . + +# collects proto specs +FROM scratch AS proto-specs +ADD api/provider/provider.proto /api/provider/ +ADD api/specs/specs.proto /api/specs/ + +# base toolchain image +FROM --platform=${BUILDPLATFORM} ${TOOLCHAIN} AS toolchain +RUN apk --update --no-cache add bash curl build-base protoc protobuf-dev + +# build tools +FROM --platform=${BUILDPLATFORM} toolchain AS tools +ENV GO111MODULE=on +ARG CGO_ENABLED +ENV CGO_ENABLED=${CGO_ENABLED} +ARG GOTOOLCHAIN +ENV GOTOOLCHAIN=${GOTOOLCHAIN} +ARG GOEXPERIMENT +ENV GOEXPERIMENT=${GOEXPERIMENT} +ENV GOPATH=/go +ARG GOIMPORTS_VERSION +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go install golang.org/x/tools/cmd/goimports@v${GOIMPORTS_VERSION} +RUN mv /go/bin/goimports /bin +ARG PROTOBUF_GO_VERSION +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go install google.golang.org/protobuf/cmd/protoc-gen-go@v${PROTOBUF_GO_VERSION} +RUN mv /go/bin/protoc-gen-go /bin +ARG GRPC_GO_VERSION +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v${GRPC_GO_VERSION} +RUN mv /go/bin/protoc-gen-go-grpc /bin +ARG GRPC_GATEWAY_VERSION +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@v${GRPC_GATEWAY_VERSION} +RUN mv /go/bin/protoc-gen-grpc-gateway /bin +ARG VTPROTOBUF_VERSION +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go install github.com/planetscale/vtprotobuf/cmd/protoc-gen-go-vtproto@v${VTPROTOBUF_VERSION} +RUN mv /go/bin/protoc-gen-go-vtproto /bin +ARG DEEPCOPY_VERSION +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go install github.com/siderolabs/deep-copy@${DEEPCOPY_VERSION} \ + && mv /go/bin/deep-copy /bin/deep-copy +ARG GOLANGCILINT_VERSION +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go install github.com/golangci/golangci-lint/cmd/golangci-lint@${GOLANGCILINT_VERSION} \ + && mv /go/bin/golangci-lint /bin/golangci-lint +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go install golang.org/x/vuln/cmd/govulncheck@latest \ + && mv /go/bin/govulncheck /bin/govulncheck +ARG GOFUMPT_VERSION +RUN go install mvdan.cc/gofumpt@${GOFUMPT_VERSION} \ + && mv /go/bin/gofumpt /bin/gofumpt + +# tools and sources +FROM tools AS base +WORKDIR /src +COPY go.mod go.mod +COPY go.sum go.sum +RUN cd . +RUN --mount=type=cache,target=/go/pkg go mod download +RUN --mount=type=cache,target=/go/pkg go mod verify +COPY ./api ./api +COPY ./cmd ./cmd +COPY ./internal ./internal +RUN --mount=type=cache,target=/go/pkg go list -mod=readonly all >/dev/null + +FROM tools AS embed-generate +ARG SHA +ARG TAG +WORKDIR /src +RUN mkdir -p internal/version/data && \ + echo -n ${SHA} > internal/version/data/sha && \ + echo -n ${TAG} > internal/version/data/tag + +# runs protobuf compiler +FROM tools AS proto-compile +COPY --from=proto-specs / / +RUN protoc -I/api --go_out=paths=source_relative:/api --go-grpc_out=paths=source_relative:/api --go-vtproto_out=paths=source_relative:/api --go-vtproto_opt=features=marshal+unmarshal+size+equal+clone /api/provider/provider.proto /api/specs/specs.proto +RUN rm /api/provider/provider.proto +RUN rm /api/specs/specs.proto +RUN goimports -w -local github.com/siderolabs/omni-infra-provider-bare-metal /api +RUN gofumpt -w /api + +# runs gofumpt +FROM base AS lint-gofumpt +RUN FILES="$(gofumpt -l .)" && test -z "${FILES}" || (echo -e "Source code is not formatted with 'gofumpt -w .':\n${FILES}"; exit 1) + +# runs golangci-lint +FROM base AS lint-golangci-lint +WORKDIR /src +COPY .golangci.yml . +ENV GOGC=50 +RUN golangci-lint config verify --config .golangci.yml +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/root/.cache/golangci-lint --mount=type=cache,target=/go/pkg golangci-lint run --config .golangci.yml + +# runs govulncheck +FROM base AS lint-govulncheck +WORKDIR /src +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg govulncheck ./... + +# runs unit-tests with race detector +FROM base AS unit-tests-race +WORKDIR /src +ARG TESTPKGS +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg --mount=type=cache,target=/tmp CGO_ENABLED=1 go test -v -race -count 1 ${TESTPKGS} + +# runs unit-tests +FROM base AS unit-tests-run +WORKDIR /src +ARG TESTPKGS +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg --mount=type=cache,target=/tmp go test -v -covermode=atomic -coverprofile=coverage.txt -coverpkg=${TESTPKGS} -count 1 ${TESTPKGS} + +FROM embed-generate AS embed-abbrev-generate +WORKDIR /src +ARG ABBREV_TAG +RUN echo -n 'undefined' > internal/version/data/sha && \ + echo -n ${ABBREV_TAG} > internal/version/data/tag + +FROM scratch AS unit-tests +COPY --from=unit-tests-run /src/coverage.txt /coverage-unit-tests.txt + +# cleaned up specs and compiled versions +FROM scratch AS generate +COPY --from=proto-compile /api/ /api/ +COPY --from=embed-abbrev-generate /src/internal/version internal/version + +# builds provider-linux-amd64 +FROM base AS provider-linux-amd64-build +COPY --from=generate / / +COPY --from=embed-generate / / +WORKDIR /src/cmd/provider +ARG GO_BUILDFLAGS +ARG GO_LDFLAGS +ARG VERSION_PKG="internal/version" +ARG SHA +ARG TAG +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg GOARCH=amd64 GOOS=linux go build ${GO_BUILDFLAGS} -ldflags "${GO_LDFLAGS} -X ${VERSION_PKG}.Name=provider -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG}" -o /provider-linux-amd64 + +# builds provider-linux-arm64 +FROM base AS provider-linux-arm64-build +COPY --from=generate / / +COPY --from=embed-generate / / +WORKDIR /src/cmd/provider +ARG GO_BUILDFLAGS +ARG GO_LDFLAGS +ARG VERSION_PKG="internal/version" +ARG SHA +ARG TAG +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg GOARCH=arm64 GOOS=linux go build ${GO_BUILDFLAGS} -ldflags "${GO_LDFLAGS} -X ${VERSION_PKG}.Name=provider -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG}" -o /provider-linux-arm64 + +FROM scratch AS provider-linux-amd64 +COPY --from=provider-linux-amd64-build /provider-linux-amd64 /provider-linux-amd64 + +FROM scratch AS provider-linux-arm64 +COPY --from=provider-linux-arm64-build /provider-linux-arm64 /provider-linux-arm64 + +FROM provider-linux-${TARGETARCH} AS provider + +FROM scratch AS provider-all +COPY --from=provider-linux-amd64 / / +COPY --from=provider-linux-arm64 / / + +FROM scratch AS image-provider +ARG TARGETARCH +COPY --from=provider provider-linux-${TARGETARCH} /provider +COPY --from=image-fhs / / +COPY --from=image-ca-certificates / / +COPY --from=ghcr.io/siderolabs/musl:v1.8.0-16-g71d23b4 / / +COPY --from=ghcr.io/siderolabs/liblzma:v1.8.0-16-g71d23b4 / / +COPY --from=ghcr.io/siderolabs/openssl:v1.8.0-16-g71d23b4 / / +COPY --from=ghcr.io/siderolabs/ipmitool:v1.8.0-16-g71d23b4 / / +COPY --from=ghcr.io/siderolabs/ipxe:v1.8.0-16-g71d23b4 /usr/libexec/zbin /bin/zbin +COPY --from=ghcr.io/siderolabs/ipxe:v1.8.0-16-g71d23b4@sha256:36f8b53a8bc50186207327f2fc46d383547473ea1b030d0474f43db3b5a29dd1 /usr/libexec/ /var/lib/ipxe/amd64 +COPY --from=ghcr.io/siderolabs/ipxe:v1.8.0-16-g71d23b4@sha256:1a704193584f0d8147e47b0e0fb0d4cc8a1102e4ff22f68ae22ccdafd98863ae /usr/libexec/ /var/lib/ipxe/arm64 +LABEL org.opencontainers.image.source=https://github.com/siderolabs/omni-infra-provider-bare-metal +ENTRYPOINT ["/provider"] + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a612ad9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c2c4153 --- /dev/null +++ b/Makefile @@ -0,0 +1,227 @@ +# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. +# +# Generated on 2024-10-23T21:12:06Z by kres 6d3cad4. + +# common variables + +SHA := $(shell git describe --match=none --always --abbrev=8 --dirty) +TAG := $(shell git describe --tag --always --dirty --match v[0-9]\*) +ABBREV_TAG := $(shell git describe --tags >/dev/null 2>/dev/null && git describe --tag --always --match v[0-9]\* --abbrev=0 || echo 'undefined') +BRANCH := $(shell git rev-parse --abbrev-ref HEAD) +ARTIFACTS := _out +IMAGE_TAG ?= $(TAG) +OPERATING_SYSTEM := $(shell uname -s | tr '[:upper:]' '[:lower:]') +GOARCH := $(shell uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/') +WITH_DEBUG ?= false +WITH_RACE ?= false +REGISTRY ?= ghcr.io +USERNAME ?= siderolabs +REGISTRY_AND_USERNAME ?= $(REGISTRY)/$(USERNAME) +PROTOBUF_GO_VERSION ?= 1.35.1 +GRPC_GO_VERSION ?= 1.5.1 +GRPC_GATEWAY_VERSION ?= 2.22.0 +VTPROTOBUF_VERSION ?= 0.6.0 +GOIMPORTS_VERSION ?= 0.26.0 +DEEPCOPY_VERSION ?= v0.5.6 +GOLANGCILINT_VERSION ?= v1.61.0 +GOFUMPT_VERSION ?= v0.7.0 +GO_VERSION ?= 1.23.2 +GO_BUILDFLAGS ?= +GO_LDFLAGS ?= +CGO_ENABLED ?= 0 +GOTOOLCHAIN ?= local +TESTPKGS ?= ./... +KRES_IMAGE ?= ghcr.io/siderolabs/kres:latest +CONFORMANCE_IMAGE ?= ghcr.io/siderolabs/conform:latest + +# docker build settings + +BUILD := docker buildx build +PLATFORM ?= linux/amd64 +PROGRESS ?= auto +PUSH ?= false +CI_ARGS ?= +COMMON_ARGS = --file=Dockerfile +COMMON_ARGS += --provenance=false +COMMON_ARGS += --progress=$(PROGRESS) +COMMON_ARGS += --platform=$(PLATFORM) +COMMON_ARGS += --push=$(PUSH) +COMMON_ARGS += --build-arg=ARTIFACTS="$(ARTIFACTS)" +COMMON_ARGS += --build-arg=SHA="$(SHA)" +COMMON_ARGS += --build-arg=TAG="$(TAG)" +COMMON_ARGS += --build-arg=ABBREV_TAG="$(ABBREV_TAG)" +COMMON_ARGS += --build-arg=USERNAME="$(USERNAME)" +COMMON_ARGS += --build-arg=REGISTRY="$(REGISTRY)" +COMMON_ARGS += --build-arg=TOOLCHAIN="$(TOOLCHAIN)" +COMMON_ARGS += --build-arg=CGO_ENABLED="$(CGO_ENABLED)" +COMMON_ARGS += --build-arg=GO_BUILDFLAGS="$(GO_BUILDFLAGS)" +COMMON_ARGS += --build-arg=GO_LDFLAGS="$(GO_LDFLAGS)" +COMMON_ARGS += --build-arg=GOTOOLCHAIN="$(GOTOOLCHAIN)" +COMMON_ARGS += --build-arg=GOEXPERIMENT="$(GOEXPERIMENT)" +COMMON_ARGS += --build-arg=PROTOBUF_GO_VERSION="$(PROTOBUF_GO_VERSION)" +COMMON_ARGS += --build-arg=GRPC_GO_VERSION="$(GRPC_GO_VERSION)" +COMMON_ARGS += --build-arg=GRPC_GATEWAY_VERSION="$(GRPC_GATEWAY_VERSION)" +COMMON_ARGS += --build-arg=VTPROTOBUF_VERSION="$(VTPROTOBUF_VERSION)" +COMMON_ARGS += --build-arg=GOIMPORTS_VERSION="$(GOIMPORTS_VERSION)" +COMMON_ARGS += --build-arg=DEEPCOPY_VERSION="$(DEEPCOPY_VERSION)" +COMMON_ARGS += --build-arg=GOLANGCILINT_VERSION="$(GOLANGCILINT_VERSION)" +COMMON_ARGS += --build-arg=GOFUMPT_VERSION="$(GOFUMPT_VERSION)" +COMMON_ARGS += --build-arg=TESTPKGS="$(TESTPKGS)" +TOOLCHAIN ?= docker.io/golang:1.23-alpine + +# help menu + +export define HELP_MENU_HEADER +# Getting Started + +To build this project, you must have the following installed: + +- git +- make +- docker (19.03 or higher) + +## Creating a Builder Instance + +The build process makes use of experimental Docker features (buildx). +To enable experimental features, add 'experimental: "true"' to '/etc/docker/daemon.json' on +Linux or enable experimental features in Docker GUI for Windows or Mac. + +To create a builder instance, run: + + docker buildx create --name local --use + +If running builds that needs to be cached aggresively create a builder instance with the following: + + docker buildx create --name local --use --config=config.toml + +config.toml contents: + +[worker.oci] + gc = true + gckeepstorage = 50000 + + [[worker.oci.gcpolicy]] + keepBytes = 10737418240 + keepDuration = 604800 + filters = [ "type==source.local", "type==exec.cachemount", "type==source.git.checkout"] + [[worker.oci.gcpolicy]] + all = true + keepBytes = 53687091200 + +If you already have a compatible builder instance, you may use that instead. + +## Artifacts + +All artifacts will be output to ./$(ARTIFACTS). Images will be tagged with the +registry "$(REGISTRY)", username "$(USERNAME)", and a dynamic tag (e.g. $(IMAGE):$(IMAGE_TAG)). +The registry and username can be overridden by exporting REGISTRY, and USERNAME +respectively. + +endef + +ifneq (, $(filter $(WITH_RACE), t true TRUE y yes 1)) +GO_BUILDFLAGS += -race +CGO_ENABLED := 1 +GO_LDFLAGS += -linkmode=external -extldflags '-static' +endif + +ifneq (, $(filter $(WITH_DEBUG), t true TRUE y yes 1)) +GO_BUILDFLAGS += -tags sidero.debug +else +GO_LDFLAGS += -s +endif + +all: unit-tests provider image-provider lint + +$(ARTIFACTS): ## Creates artifacts directory. + @mkdir -p $(ARTIFACTS) + +.PHONY: clean +clean: ## Cleans up all artifacts. + @rm -rf $(ARTIFACTS) + +target-%: ## Builds the specified target defined in the Dockerfile. The build result will only remain in the build cache. + @$(BUILD) --target=$* $(COMMON_ARGS) $(TARGET_ARGS) $(CI_ARGS) . + +local-%: ## Builds the specified target defined in the Dockerfile using the local output type. The build result will be output to the specified local destination. + @$(MAKE) target-$* TARGET_ARGS="--output=type=local,dest=$(DEST) $(TARGET_ARGS)" + +generate: ## Generate .proto definitions. + @$(MAKE) local-$@ DEST=./ + +lint-golangci-lint: ## Runs golangci-lint linter. + @$(MAKE) target-$@ + +lint-gofumpt: ## Runs gofumpt linter. + @$(MAKE) target-$@ + +.PHONY: fmt +fmt: ## Formats the source code + @docker run --rm -it -v $(PWD):/src -w /src golang:$(GO_VERSION) \ + bash -c "export GOTOOLCHAIN=local; \ + export GO111MODULE=on; export GOPROXY=https://proxy.golang.org; \ + go install mvdan.cc/gofumpt@$(GOFUMPT_VERSION) && \ + gofumpt -w ." + +lint-govulncheck: ## Runs govulncheck linter. + @$(MAKE) target-$@ + +.PHONY: base +base: ## Prepare base toolchain + @$(MAKE) target-$@ + +.PHONY: unit-tests +unit-tests: ## Performs unit tests + @$(MAKE) local-$@ DEST=$(ARTIFACTS) + +.PHONY: unit-tests-race +unit-tests-race: ## Performs unit tests with race detection enabled. + @$(MAKE) target-$@ + +.PHONY: $(ARTIFACTS)/provider-linux-amd64 +$(ARTIFACTS)/provider-linux-amd64: + @$(MAKE) local-provider-linux-amd64 DEST=$(ARTIFACTS) + +.PHONY: provider-linux-amd64 +provider-linux-amd64: $(ARTIFACTS)/provider-linux-amd64 ## Builds executable for provider-linux-amd64. + +.PHONY: $(ARTIFACTS)/provider-linux-arm64 +$(ARTIFACTS)/provider-linux-arm64: + @$(MAKE) local-provider-linux-arm64 DEST=$(ARTIFACTS) + +.PHONY: provider-linux-arm64 +provider-linux-arm64: $(ARTIFACTS)/provider-linux-arm64 ## Builds executable for provider-linux-arm64. + +.PHONY: provider +provider: provider-linux-amd64 provider-linux-arm64 ## Builds executables for provider. + +.PHONY: lint-markdown +lint-markdown: ## Runs markdownlint. + @$(MAKE) target-$@ + +.PHONY: lint +lint: lint-golangci-lint lint-gofumpt lint-govulncheck lint-markdown ## Run all linters for the project. + +.PHONY: image-provider +image-provider: ## Builds image for provider. + @$(MAKE) target-$@ TARGET_ARGS="--tag=$(REGISTRY)/$(USERNAME)/provider:$(IMAGE_TAG)" + +.PHONY: rekres +rekres: + @docker pull $(KRES_IMAGE) + @docker run --rm --net=host --user $(shell id -u):$(shell id -g) -v $(PWD):/src -w /src -e GITHUB_TOKEN $(KRES_IMAGE) + +.PHONY: help +help: ## This help menu. + @echo "$$HELP_MENU_HEADER" + @grep -E '^[a-zA-Z%_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +.PHONY: release-notes +release-notes: $(ARTIFACTS) + @ARTIFACTS=$(ARTIFACTS) ./hack/release.sh $@ $(ARTIFACTS)/RELEASE_NOTES.md $(TAG) + +.PHONY: conformance +conformance: + @docker pull $(CONFORMANCE_IMAGE) + @docker run --rm -it -v $(PWD):/src -w /src $(CONFORMANCE_IMAGE) enforce + diff --git a/README.md b/README.md index 8586b13..9734a81 100644 --- a/README.md +++ b/README.md @@ -1 +1,6 @@ # omni-infra-provider-bare-metal + +This repo contains the code of the following: + +- Omni bare metal infra provider +- Talos metal agent service diff --git a/api/provider/provider.pb.go b/api/provider/provider.pb.go new file mode 100644 index 0000000..9d7aa5a --- /dev/null +++ b/api/provider/provider.pb.go @@ -0,0 +1,378 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.1 +// protoc v4.24.4 +// source: provider/provider.proto + +package providerpb + +import ( + reflect "reflect" + sync "sync" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + _ "google.golang.org/protobuf/types/known/durationpb" + _ "google.golang.org/protobuf/types/known/emptypb" + _ "google.golang.org/protobuf/types/known/timestamppb" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ProvisionMachineRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *ProvisionMachineRequest) Reset() { + *x = ProvisionMachineRequest{} + mi := &file_provider_provider_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProvisionMachineRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProvisionMachineRequest) ProtoMessage() {} + +func (x *ProvisionMachineRequest) ProtoReflect() protoreflect.Message { + mi := &file_provider_provider_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProvisionMachineRequest.ProtoReflect.Descriptor instead. +func (*ProvisionMachineRequest) Descriptor() ([]byte, []int) { + return file_provider_provider_proto_rawDescGZIP(), []int{0} +} + +func (x *ProvisionMachineRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type ProvisionMachineResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ProvisionMachineResponse) Reset() { + *x = ProvisionMachineResponse{} + mi := &file_provider_provider_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProvisionMachineResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProvisionMachineResponse) ProtoMessage() {} + +func (x *ProvisionMachineResponse) ProtoReflect() protoreflect.Message { + mi := &file_provider_provider_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProvisionMachineResponse.ProtoReflect.Descriptor instead. +func (*ProvisionMachineResponse) Descriptor() ([]byte, []int) { + return file_provider_provider_proto_rawDescGZIP(), []int{1} +} + +type DeprovisionMachineRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *DeprovisionMachineRequest) Reset() { + *x = DeprovisionMachineRequest{} + mi := &file_provider_provider_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeprovisionMachineRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeprovisionMachineRequest) ProtoMessage() {} + +func (x *DeprovisionMachineRequest) ProtoReflect() protoreflect.Message { + mi := &file_provider_provider_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeprovisionMachineRequest.ProtoReflect.Descriptor instead. +func (*DeprovisionMachineRequest) Descriptor() ([]byte, []int) { + return file_provider_provider_proto_rawDescGZIP(), []int{2} +} + +func (x *DeprovisionMachineRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type DeprovisionMachineResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DeprovisionMachineResponse) Reset() { + *x = DeprovisionMachineResponse{} + mi := &file_provider_provider_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeprovisionMachineResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeprovisionMachineResponse) ProtoMessage() {} + +func (x *DeprovisionMachineResponse) ProtoReflect() protoreflect.Message { + mi := &file_provider_provider_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeprovisionMachineResponse.ProtoReflect.Descriptor instead. +func (*DeprovisionMachineResponse) Descriptor() ([]byte, []int) { + return file_provider_provider_proto_rawDescGZIP(), []int{3} +} + +type PowerOnMachineRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *PowerOnMachineRequest) Reset() { + *x = PowerOnMachineRequest{} + mi := &file_provider_provider_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PowerOnMachineRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PowerOnMachineRequest) ProtoMessage() {} + +func (x *PowerOnMachineRequest) ProtoReflect() protoreflect.Message { + mi := &file_provider_provider_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PowerOnMachineRequest.ProtoReflect.Descriptor instead. +func (*PowerOnMachineRequest) Descriptor() ([]byte, []int) { + return file_provider_provider_proto_rawDescGZIP(), []int{4} +} + +func (x *PowerOnMachineRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type PowerOnMachineResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *PowerOnMachineResponse) Reset() { + *x = PowerOnMachineResponse{} + mi := &file_provider_provider_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PowerOnMachineResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PowerOnMachineResponse) ProtoMessage() {} + +func (x *PowerOnMachineResponse) ProtoReflect() protoreflect.Message { + mi := &file_provider_provider_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PowerOnMachineResponse.ProtoReflect.Descriptor instead. +func (*PowerOnMachineResponse) Descriptor() ([]byte, []int) { + return file_provider_provider_proto_rawDescGZIP(), []int{5} +} + +var File_provider_provider_proto protoreflect.FileDescriptor + +var file_provider_provider_proto_rawDesc = []byte{ + 0x0a, 0x17, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0x29, 0x0a, 0x17, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x1a, + 0x0a, 0x18, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x63, 0x68, 0x69, + 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x0a, 0x19, 0x44, 0x65, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x1c, 0x0a, 0x1a, 0x44, 0x65, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x27, 0x0a, 0x15, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x4f, 0x6e, + 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x18, + 0x0a, 0x16, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x4f, 0x6e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xae, 0x02, 0x0a, 0x0f, 0x50, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5d, 0x0a, 0x10, + 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, + 0x12, 0x23, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x63, 0x68, + 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x12, 0x44, + 0x65, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, + 0x65, 0x12, 0x25, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x44, + 0x65, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x65, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x57, 0x0a, 0x0e, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x4f, 0x6e, 0x4d, 0x61, 0x63, 0x68, 0x69, + 0x6e, 0x65, 0x12, 0x21, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, + 0x50, 0x6f, 0x77, 0x65, 0x72, 0x4f, 0x6e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x4f, 0x6e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, 0x64, 0x65, 0x72, 0x6f, 0x6c, 0x61, + 0x62, 0x73, 0x2f, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2d, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x2d, 0x61, + 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_provider_provider_proto_rawDescOnce sync.Once + file_provider_provider_proto_rawDescData = file_provider_provider_proto_rawDesc +) + +func file_provider_provider_proto_rawDescGZIP() []byte { + file_provider_provider_proto_rawDescOnce.Do(func() { + file_provider_provider_proto_rawDescData = protoimpl.X.CompressGZIP(file_provider_provider_proto_rawDescData) + }) + return file_provider_provider_proto_rawDescData +} + +var file_provider_provider_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_provider_provider_proto_goTypes = []any{ + (*ProvisionMachineRequest)(nil), // 0: management.ProvisionMachineRequest + (*ProvisionMachineResponse)(nil), // 1: management.ProvisionMachineResponse + (*DeprovisionMachineRequest)(nil), // 2: management.DeprovisionMachineRequest + (*DeprovisionMachineResponse)(nil), // 3: management.DeprovisionMachineResponse + (*PowerOnMachineRequest)(nil), // 4: management.PowerOnMachineRequest + (*PowerOnMachineResponse)(nil), // 5: management.PowerOnMachineResponse +} +var file_provider_provider_proto_depIdxs = []int32{ + 0, // 0: management.ProviderService.ProvisionMachine:input_type -> management.ProvisionMachineRequest + 2, // 1: management.ProviderService.DeprovisionMachine:input_type -> management.DeprovisionMachineRequest + 4, // 2: management.ProviderService.PowerOnMachine:input_type -> management.PowerOnMachineRequest + 1, // 3: management.ProviderService.ProvisionMachine:output_type -> management.ProvisionMachineResponse + 3, // 4: management.ProviderService.DeprovisionMachine:output_type -> management.DeprovisionMachineResponse + 5, // 5: management.ProviderService.PowerOnMachine:output_type -> management.PowerOnMachineResponse + 3, // [3:6] is the sub-list for method output_type + 0, // [0:3] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_provider_provider_proto_init() } +func file_provider_provider_proto_init() { + if File_provider_provider_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_provider_provider_proto_rawDesc, + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_provider_provider_proto_goTypes, + DependencyIndexes: file_provider_provider_proto_depIdxs, + MessageInfos: file_provider_provider_proto_msgTypes, + }.Build() + File_provider_provider_proto = out.File + file_provider_provider_proto_rawDesc = nil + file_provider_provider_proto_goTypes = nil + file_provider_provider_proto_depIdxs = nil +} diff --git a/api/provider/provider.proto b/api/provider/provider.proto new file mode 100644 index 0000000..d840c10 --- /dev/null +++ b/api/provider/provider.proto @@ -0,0 +1,35 @@ +syntax = "proto3"; +package management; + +option go_package = "github.com/siderolabs/talos-metal-agent/internal/providerpb"; + +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/duration.proto"; + +message ProvisionMachineRequest { + string id = 1; +} + +message ProvisionMachineResponse { +} + +message DeprovisionMachineRequest { + string id = 1; +} + +message DeprovisionMachineResponse { +} + +message PowerOnMachineRequest { + string id = 1; +} + +message PowerOnMachineResponse { +} + +service ProviderService { + rpc ProvisionMachine(ProvisionMachineRequest) returns (ProvisionMachineResponse); + rpc DeprovisionMachine(DeprovisionMachineRequest) returns (DeprovisionMachineResponse); + rpc PowerOnMachine(PowerOnMachineRequest) returns (PowerOnMachineResponse); +} diff --git a/api/provider/provider_grpc.pb.go b/api/provider/provider_grpc.pb.go new file mode 100644 index 0000000..ac77988 --- /dev/null +++ b/api/provider/provider_grpc.pb.go @@ -0,0 +1,198 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v4.24.4 +// source: provider/provider.proto + +package providerpb + +import ( + context "context" + + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + ProviderService_ProvisionMachine_FullMethodName = "/management.ProviderService/ProvisionMachine" + ProviderService_DeprovisionMachine_FullMethodName = "/management.ProviderService/DeprovisionMachine" + ProviderService_PowerOnMachine_FullMethodName = "/management.ProviderService/PowerOnMachine" +) + +// ProviderServiceClient is the client API for ProviderService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ProviderServiceClient interface { + ProvisionMachine(ctx context.Context, in *ProvisionMachineRequest, opts ...grpc.CallOption) (*ProvisionMachineResponse, error) + DeprovisionMachine(ctx context.Context, in *DeprovisionMachineRequest, opts ...grpc.CallOption) (*DeprovisionMachineResponse, error) + PowerOnMachine(ctx context.Context, in *PowerOnMachineRequest, opts ...grpc.CallOption) (*PowerOnMachineResponse, error) +} + +type providerServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewProviderServiceClient(cc grpc.ClientConnInterface) ProviderServiceClient { + return &providerServiceClient{cc} +} + +func (c *providerServiceClient) ProvisionMachine(ctx context.Context, in *ProvisionMachineRequest, opts ...grpc.CallOption) (*ProvisionMachineResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ProvisionMachineResponse) + err := c.cc.Invoke(ctx, ProviderService_ProvisionMachine_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerServiceClient) DeprovisionMachine(ctx context.Context, in *DeprovisionMachineRequest, opts ...grpc.CallOption) (*DeprovisionMachineResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeprovisionMachineResponse) + err := c.cc.Invoke(ctx, ProviderService_DeprovisionMachine_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerServiceClient) PowerOnMachine(ctx context.Context, in *PowerOnMachineRequest, opts ...grpc.CallOption) (*PowerOnMachineResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PowerOnMachineResponse) + err := c.cc.Invoke(ctx, ProviderService_PowerOnMachine_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ProviderServiceServer is the server API for ProviderService service. +// All implementations must embed UnimplementedProviderServiceServer +// for forward compatibility. +type ProviderServiceServer interface { + ProvisionMachine(context.Context, *ProvisionMachineRequest) (*ProvisionMachineResponse, error) + DeprovisionMachine(context.Context, *DeprovisionMachineRequest) (*DeprovisionMachineResponse, error) + PowerOnMachine(context.Context, *PowerOnMachineRequest) (*PowerOnMachineResponse, error) + mustEmbedUnimplementedProviderServiceServer() +} + +// UnimplementedProviderServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedProviderServiceServer struct{} + +func (UnimplementedProviderServiceServer) ProvisionMachine(context.Context, *ProvisionMachineRequest) (*ProvisionMachineResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ProvisionMachine not implemented") +} +func (UnimplementedProviderServiceServer) DeprovisionMachine(context.Context, *DeprovisionMachineRequest) (*DeprovisionMachineResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeprovisionMachine not implemented") +} +func (UnimplementedProviderServiceServer) PowerOnMachine(context.Context, *PowerOnMachineRequest) (*PowerOnMachineResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PowerOnMachine not implemented") +} +func (UnimplementedProviderServiceServer) mustEmbedUnimplementedProviderServiceServer() {} +func (UnimplementedProviderServiceServer) testEmbeddedByValue() {} + +// UnsafeProviderServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ProviderServiceServer will +// result in compilation errors. +type UnsafeProviderServiceServer interface { + mustEmbedUnimplementedProviderServiceServer() +} + +func RegisterProviderServiceServer(s grpc.ServiceRegistrar, srv ProviderServiceServer) { + // If the following call pancis, it indicates UnimplementedProviderServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&ProviderService_ServiceDesc, srv) +} + +func _ProviderService_ProvisionMachine_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ProvisionMachineRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServiceServer).ProvisionMachine(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ProviderService_ProvisionMachine_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServiceServer).ProvisionMachine(ctx, req.(*ProvisionMachineRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ProviderService_DeprovisionMachine_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeprovisionMachineRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServiceServer).DeprovisionMachine(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ProviderService_DeprovisionMachine_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServiceServer).DeprovisionMachine(ctx, req.(*DeprovisionMachineRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ProviderService_PowerOnMachine_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PowerOnMachineRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServiceServer).PowerOnMachine(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ProviderService_PowerOnMachine_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServiceServer).PowerOnMachine(ctx, req.(*PowerOnMachineRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// ProviderService_ServiceDesc is the grpc.ServiceDesc for ProviderService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ProviderService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "management.ProviderService", + HandlerType: (*ProviderServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ProvisionMachine", + Handler: _ProviderService_ProvisionMachine_Handler, + }, + { + MethodName: "DeprovisionMachine", + Handler: _ProviderService_DeprovisionMachine_Handler, + }, + { + MethodName: "PowerOnMachine", + Handler: _ProviderService_PowerOnMachine_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "provider/provider.proto", +} diff --git a/api/provider/provider_vtproto.pb.go b/api/provider/provider_vtproto.pb.go new file mode 100644 index 0000000..d74d0f2 --- /dev/null +++ b/api/provider/provider_vtproto.pb.go @@ -0,0 +1,919 @@ +// Code generated by protoc-gen-go-vtproto. DO NOT EDIT. +// protoc-gen-go-vtproto version: v0.6.0 +// source: provider/provider.proto + +package providerpb + +import ( + fmt "fmt" + io "io" + + protohelpers "github.com/planetscale/vtprotobuf/protohelpers" + proto "google.golang.org/protobuf/proto" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +func (m *ProvisionMachineRequest) CloneVT() *ProvisionMachineRequest { + if m == nil { + return (*ProvisionMachineRequest)(nil) + } + r := new(ProvisionMachineRequest) + r.Id = m.Id + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *ProvisionMachineRequest) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (m *ProvisionMachineResponse) CloneVT() *ProvisionMachineResponse { + if m == nil { + return (*ProvisionMachineResponse)(nil) + } + r := new(ProvisionMachineResponse) + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *ProvisionMachineResponse) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (m *DeprovisionMachineRequest) CloneVT() *DeprovisionMachineRequest { + if m == nil { + return (*DeprovisionMachineRequest)(nil) + } + r := new(DeprovisionMachineRequest) + r.Id = m.Id + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *DeprovisionMachineRequest) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (m *DeprovisionMachineResponse) CloneVT() *DeprovisionMachineResponse { + if m == nil { + return (*DeprovisionMachineResponse)(nil) + } + r := new(DeprovisionMachineResponse) + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *DeprovisionMachineResponse) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (m *PowerOnMachineRequest) CloneVT() *PowerOnMachineRequest { + if m == nil { + return (*PowerOnMachineRequest)(nil) + } + r := new(PowerOnMachineRequest) + r.Id = m.Id + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *PowerOnMachineRequest) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (m *PowerOnMachineResponse) CloneVT() *PowerOnMachineResponse { + if m == nil { + return (*PowerOnMachineResponse)(nil) + } + r := new(PowerOnMachineResponse) + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *PowerOnMachineResponse) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (this *ProvisionMachineRequest) EqualVT(that *ProvisionMachineRequest) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.Id != that.Id { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *ProvisionMachineRequest) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*ProvisionMachineRequest) + if !ok { + return false + } + return this.EqualVT(that) +} +func (this *ProvisionMachineResponse) EqualVT(that *ProvisionMachineResponse) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *ProvisionMachineResponse) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*ProvisionMachineResponse) + if !ok { + return false + } + return this.EqualVT(that) +} +func (this *DeprovisionMachineRequest) EqualVT(that *DeprovisionMachineRequest) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.Id != that.Id { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DeprovisionMachineRequest) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*DeprovisionMachineRequest) + if !ok { + return false + } + return this.EqualVT(that) +} +func (this *DeprovisionMachineResponse) EqualVT(that *DeprovisionMachineResponse) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DeprovisionMachineResponse) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*DeprovisionMachineResponse) + if !ok { + return false + } + return this.EqualVT(that) +} +func (this *PowerOnMachineRequest) EqualVT(that *PowerOnMachineRequest) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.Id != that.Id { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *PowerOnMachineRequest) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*PowerOnMachineRequest) + if !ok { + return false + } + return this.EqualVT(that) +} +func (this *PowerOnMachineResponse) EqualVT(that *PowerOnMachineResponse) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *PowerOnMachineResponse) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*PowerOnMachineResponse) + if !ok { + return false + } + return this.EqualVT(that) +} +func (m *ProvisionMachineRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ProvisionMachineRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ProvisionMachineRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.Id) > 0 { + i -= len(m.Id) + copy(dAtA[i:], m.Id) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Id))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ProvisionMachineResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ProvisionMachineResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ProvisionMachineResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + return len(dAtA) - i, nil +} + +func (m *DeprovisionMachineRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeprovisionMachineRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *DeprovisionMachineRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.Id) > 0 { + i -= len(m.Id) + copy(dAtA[i:], m.Id) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Id))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *DeprovisionMachineResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeprovisionMachineResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *DeprovisionMachineResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + return len(dAtA) - i, nil +} + +func (m *PowerOnMachineRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PowerOnMachineRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *PowerOnMachineRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.Id) > 0 { + i -= len(m.Id) + copy(dAtA[i:], m.Id) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Id))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *PowerOnMachineResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PowerOnMachineResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *PowerOnMachineResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + return len(dAtA) - i, nil +} + +func (m *ProvisionMachineRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Id) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *ProvisionMachineResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + n += len(m.unknownFields) + return n +} + +func (m *DeprovisionMachineRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Id) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *DeprovisionMachineResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + n += len(m.unknownFields) + return n +} + +func (m *PowerOnMachineRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Id) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *PowerOnMachineResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + n += len(m.unknownFields) + return n +} + +func (m *ProvisionMachineRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ProvisionMachineRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ProvisionMachineRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Id = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ProvisionMachineResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ProvisionMachineResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ProvisionMachineResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeprovisionMachineRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeprovisionMachineRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeprovisionMachineRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Id = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeprovisionMachineResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeprovisionMachineResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeprovisionMachineResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PowerOnMachineRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PowerOnMachineRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PowerOnMachineRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Id = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PowerOnMachineResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PowerOnMachineResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PowerOnMachineResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} diff --git a/api/specs/specs.pb.go b/api/specs/specs.pb.go new file mode 100644 index 0000000..f095c0b --- /dev/null +++ b/api/specs/specs.pb.go @@ -0,0 +1,157 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.1 +// protoc v4.24.4 +// source: specs/specs.proto + +package specs + +import ( + reflect "reflect" + sync "sync" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + _ "google.golang.org/protobuf/types/known/durationpb" + _ "google.golang.org/protobuf/types/known/timestamppb" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// MachineSpec is stored in Omni in the infra provisioner state. +type MachineSpec struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + IpmiIp string `protobuf:"bytes,1,opt,name=ipmi_ip,json=ipmiIp,proto3" json:"ipmi_ip,omitempty"` + IpmiPort uint32 `protobuf:"varint,2,opt,name=ipmi_port,json=ipmiPort,proto3" json:"ipmi_port,omitempty"` + IpmiPassword string `protobuf:"bytes,3,opt,name=ipmi_password,json=ipmiPassword,proto3" json:"ipmi_password,omitempty"` +} + +func (x *MachineSpec) Reset() { + *x = MachineSpec{} + mi := &file_specs_specs_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MachineSpec) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MachineSpec) ProtoMessage() {} + +func (x *MachineSpec) ProtoReflect() protoreflect.Message { + mi := &file_specs_specs_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MachineSpec.ProtoReflect.Descriptor instead. +func (*MachineSpec) Descriptor() ([]byte, []int) { + return file_specs_specs_proto_rawDescGZIP(), []int{0} +} + +func (x *MachineSpec) GetIpmiIp() string { + if x != nil { + return x.IpmiIp + } + return "" +} + +func (x *MachineSpec) GetIpmiPort() uint32 { + if x != nil { + return x.IpmiPort + } + return 0 +} + +func (x *MachineSpec) GetIpmiPassword() string { + if x != nil { + return x.IpmiPassword + } + return "" +} + +var File_specs_specs_proto protoreflect.FileDescriptor + +var file_specs_specs_proto_rawDesc = []byte{ + 0x0a, 0x11, 0x73, 0x70, 0x65, 0x63, 0x73, 0x2f, 0x73, 0x70, 0x65, 0x63, 0x73, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x65, 0x6d, 0x75, 0x73, 0x70, 0x65, 0x63, 0x73, 0x1a, 0x1f, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, + 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x68, + 0x0a, 0x0b, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x17, 0x0a, + 0x07, 0x69, 0x70, 0x6d, 0x69, 0x5f, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x69, 0x70, 0x6d, 0x69, 0x49, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x70, 0x6d, 0x69, 0x5f, 0x70, + 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x69, 0x70, 0x6d, 0x69, 0x50, + 0x6f, 0x72, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x70, 0x6d, 0x69, 0x5f, 0x70, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x69, 0x70, 0x6d, 0x69, + 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x42, 0x3e, 0x5a, 0x3c, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, 0x64, 0x65, 0x72, 0x6f, 0x6c, 0x61, 0x62, + 0x73, 0x2f, 0x6f, 0x6d, 0x6e, 0x69, 0x2d, 0x69, 0x6e, 0x66, 0x72, 0x61, 0x2d, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x72, 0x2d, 0x6b, 0x75, 0x62, 0x65, 0x76, 0x69, 0x72, 0x74, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x73, 0x70, 0x65, 0x63, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_specs_specs_proto_rawDescOnce sync.Once + file_specs_specs_proto_rawDescData = file_specs_specs_proto_rawDesc +) + +func file_specs_specs_proto_rawDescGZIP() []byte { + file_specs_specs_proto_rawDescOnce.Do(func() { + file_specs_specs_proto_rawDescData = protoimpl.X.CompressGZIP(file_specs_specs_proto_rawDescData) + }) + return file_specs_specs_proto_rawDescData +} + +var file_specs_specs_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_specs_specs_proto_goTypes = []any{ + (*MachineSpec)(nil), // 0: emuspecs.MachineSpec +} +var file_specs_specs_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_specs_specs_proto_init() } +func file_specs_specs_proto_init() { + if File_specs_specs_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_specs_specs_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_specs_specs_proto_goTypes, + DependencyIndexes: file_specs_specs_proto_depIdxs, + MessageInfos: file_specs_specs_proto_msgTypes, + }.Build() + File_specs_specs_proto = out.File + file_specs_specs_proto_rawDesc = nil + file_specs_specs_proto_goTypes = nil + file_specs_specs_proto_depIdxs = nil +} diff --git a/api/specs/specs.proto b/api/specs/specs.proto new file mode 100644 index 0000000..0170533 --- /dev/null +++ b/api/specs/specs.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; +package emuspecs; + +option go_package = "github.com/siderolabs/omni-infra-provider-kubevirt/api/specs"; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/duration.proto"; + +// MachineSpec is stored in Omni in the infra provisioner state. +message MachineSpec { + string ipmi_ip = 1; + uint32 ipmi_port = 2; + string ipmi_password = 3; +} diff --git a/api/specs/specs_vtproto.pb.go b/api/specs/specs_vtproto.pb.go new file mode 100644 index 0000000..c73bdc0 --- /dev/null +++ b/api/specs/specs_vtproto.pb.go @@ -0,0 +1,273 @@ +// Code generated by protoc-gen-go-vtproto. DO NOT EDIT. +// protoc-gen-go-vtproto version: v0.6.0 +// source: specs/specs.proto + +package specs + +import ( + fmt "fmt" + io "io" + + protohelpers "github.com/planetscale/vtprotobuf/protohelpers" + proto "google.golang.org/protobuf/proto" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +func (m *MachineSpec) CloneVT() *MachineSpec { + if m == nil { + return (*MachineSpec)(nil) + } + r := new(MachineSpec) + r.IpmiIp = m.IpmiIp + r.IpmiPort = m.IpmiPort + r.IpmiPassword = m.IpmiPassword + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *MachineSpec) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (this *MachineSpec) EqualVT(that *MachineSpec) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.IpmiIp != that.IpmiIp { + return false + } + if this.IpmiPort != that.IpmiPort { + return false + } + if this.IpmiPassword != that.IpmiPassword { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *MachineSpec) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*MachineSpec) + if !ok { + return false + } + return this.EqualVT(that) +} +func (m *MachineSpec) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MachineSpec) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *MachineSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.IpmiPassword) > 0 { + i -= len(m.IpmiPassword) + copy(dAtA[i:], m.IpmiPassword) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.IpmiPassword))) + i-- + dAtA[i] = 0x1a + } + if m.IpmiPort != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.IpmiPort)) + i-- + dAtA[i] = 0x10 + } + if len(m.IpmiIp) > 0 { + i -= len(m.IpmiIp) + copy(dAtA[i:], m.IpmiIp) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.IpmiIp))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MachineSpec) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.IpmiIp) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.IpmiPort != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.IpmiPort)) + } + l = len(m.IpmiPassword) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *MachineSpec) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MachineSpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MachineSpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field IpmiIp", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.IpmiIp = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IpmiPort", wireType) + } + m.IpmiPort = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.IpmiPort |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field IpmiPassword", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.IpmiPassword = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} diff --git a/cmd/provider/main.go b/cmd/provider/main.go new file mode 100644 index 0000000..c49b0f9 --- /dev/null +++ b/cmd/provider/main.go @@ -0,0 +1,162 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package main implements the main entrypoint for the Omni bare metal infra provider. +package main + +import ( + "context" + "fmt" + "log" + "os" + "os/signal" + "syscall" + + "github.com/spf13/cobra" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/ip" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/meta" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/version" +) + +const apiAdvertiseAddressFlag = "api-advertise-address" + +var rootCmdArgs struct { + apiListenAddress string + apiAdvertiseAddress string + omniAPIEndpoint string + imageFactoryPXEURL string + providerName string + providerDescription string + agentTalosImage string + imageFactoryBaseURL string + imageFactoryPXEBaseURL string + imageFactoryTalosVersion string + machineLabels []string + apiPort int + insecureSkipTLSVerify bool + debug bool +} + +// rootCmd represents the base command when called without any subcommands. +var rootCmd = &cobra.Command{ + Use: version.Name, + Short: "Run the Omni bare metal infra provider", + Version: version.Tag, + Args: cobra.NoArgs, + PersistentPreRun: func(cmd *cobra.Command, _ []string) { + cmd.SilenceUsage = true // if the args are parsed fine, no need to show usage + }, + RunE: func(cmd *cobra.Command, _ []string) error { + logger, err := initLogger() + if err != nil { + return fmt.Errorf("failed to create logger: %w", err) + } + + defer logger.Sync() //nolint:errcheck + + return run(cmd.Context(), logger) + }, +} + +func initLogger() (*zap.Logger, error) { + var loggerConfig zap.Config + + if rootCmdArgs.debug { + loggerConfig = zap.NewDevelopmentConfig() + loggerConfig.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder + loggerConfig.Level.SetLevel(zap.DebugLevel) + } else { + loggerConfig = zap.NewProductionConfig() + loggerConfig.Level.SetLevel(zap.InfoLevel) + } + + return loggerConfig.Build() +} + +func run(ctx context.Context, logger *zap.Logger) error { + apiAdvertiseAddress := rootCmdArgs.apiAdvertiseAddress + + if apiAdvertiseAddress == "" { + routableIPs, err := ip.RoutableIPs() + if err != nil { + return fmt.Errorf("failed to get routable IPs: %w", err) + } + + if len(routableIPs) != 1 { + return fmt.Errorf(`expected exactly one routable IP, got %d: %v. specify "--%s" flag explicitly`, len(routableIPs), routableIPs, apiAdvertiseAddressFlag) + } + + apiAdvertiseAddress = routableIPs[0] + } + + logger.Info("starting provider", zap.String("api_host", apiAdvertiseAddress), zap.Int("api_port", rootCmdArgs.apiPort)) + + options := provider.Options{ + Name: rootCmdArgs.providerName, + Description: rootCmdArgs.providerDescription, + OmniAPIEndpoint: rootCmdArgs.omniAPIEndpoint, + APIListenAddress: rootCmdArgs.apiListenAddress, + APIAdvertiseAddress: rootCmdArgs.apiAdvertiseAddress, + APIPort: rootCmdArgs.apiPort, + AgentTalosImage: rootCmdArgs.agentTalosImage, + ImageFactoryBaseURL: rootCmdArgs.imageFactoryBaseURL, + ImageFactoryPXEBaseURL: rootCmdArgs.imageFactoryPXEBaseURL, + ImageFactoryTalosVersion: rootCmdArgs.imageFactoryTalosVersion, + MachineLabels: rootCmdArgs.machineLabels, + InsecureSkipTLSVerify: rootCmdArgs.insecureSkipTLSVerify, + } + + prov := provider.New(options, logger) + + if err := prov.Run(ctx); err != nil { + return fmt.Errorf("failed to run provider: %w", err) + } + + return nil +} + +func main() { + if err := runCmd(); err != nil { + log.Fatalf("failed to run: %v", err) + } +} + +func runCmd() error { + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, os.Interrupt) + defer cancel() + + return rootCmd.ExecuteContext(ctx) +} + +func init() { + rootCmd.Flags().StringVar(&rootCmdArgs.apiListenAddress, "api-listen-address", "", "The IP address to listen on. If not specified, the server will listen on all interfaces.") + rootCmd.Flags().StringVar(&rootCmdArgs.apiAdvertiseAddress, apiAdvertiseAddressFlag, "", + "The IP address to advertise. Required if the server has more than a single routable IP address. If not specified, the single routable IP address will be used.") + + rootCmd.Flags().IntVar(&rootCmdArgs.apiPort, "api-port", 50042, "The port to run the api server on.") + rootCmd.Flags().StringVar(&rootCmdArgs.omniAPIEndpoint, "omni-api-endpoint", os.Getenv("OMNI_ENDPOINT"), + "The endpoint of the Omni API, if not set, defaults to OMNI_ENDPOINT env var.") + rootCmd.Flags().StringVar(&meta.ProviderID, "id", meta.ProviderID, "The id of the infra provider, it is used to match the resources with the infra provider label.") + rootCmd.Flags().StringVar(&rootCmdArgs.imageFactoryPXEURL, "image-factory-pxe-url", "https://pxe.factory.talos.dev", "The URL of the image factory PXE server.") + + rootCmd.Flags().StringVar(&rootCmdArgs.providerName, "provider-name", "Bare Metal", "Provider name as it appears in Omni") + rootCmd.Flags().StringVar(&rootCmdArgs.providerDescription, "provider-description", "Bare metal infrastructure provider", "Provider description as it appears in Omni") + + rootCmd.Flags().StringVar(&rootCmdArgs.agentTalosImage, "agent-talos-image", "", "The Talos metal agent mode image mainly to be used for debugging purposes. If specified, "+ + "the iPXE server will use the kernel and initramfs from this image instead of forwarding the request to the image factory to boot into agent mode.") + rootCmd.Flags().StringVar(&rootCmdArgs.imageFactoryBaseURL, "image-factory-base-url", "https://factory.talos.dev", "The base URL of the image factory.") + rootCmd.Flags().StringVar(&rootCmdArgs.imageFactoryPXEBaseURL, "image-factory-pxe-base-url", "https://pxe.factory.talos.dev", "The base URL of the image factory PXE server.") + rootCmd.Flags().StringVar(&rootCmdArgs.imageFactoryTalosVersion, "image-factory-talos-version", "v1.8.1", + "The Talos version to when forwarding iPXE requests to the image factory.") + + rootCmd.Flags().StringSliceVar(&rootCmdArgs.machineLabels, "machine-labels", nil, + "Comma separated list of key=value pairs to be set to the machine. Example: key1=value1,key2,key3=value3") + + rootCmd.Flags().BoolVar(&rootCmdArgs.insecureSkipTLSVerify, "insecure-skip-tls-verify", false, "Skip TLS verification when connecting to the Omni API.") + rootCmd.Flags().BoolVar(&rootCmdArgs.debug, "debug", false, "Enable debug mode & logs.") +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5eb5291 --- /dev/null +++ b/go.mod @@ -0,0 +1,85 @@ +module github.com/siderolabs/omni-infra-provider-bare-metal + +go 1.23.2 + +replace ( + github.com/pensando/goipmi v0.0.0-20240603174436-eb122d901c23 => github.com/siderolabs/goipmi v0.0.0-20211214143420-35f956689e67 + github.com/pin/tftp/v3 v3.1.0 => github.com/utkuozdemir/pin-tftp/v3 v3.0.0-20241021135417-0dd7dba351ad + github.com/siderolabs/omni/client v0.0.0-20241017162757-284e8b5077cc => github.com/utkuozdemir/sidero-omni/client v0.0.0-20241025225840-6bccefcfa215 + github.com/siderolabs/talos-metal-agent v0.0.0-20241016074728-46df49991336 => github.com/utkuozdemir/sidero-talos-metal-agent v0.0.0-20241023212532-42f37aaacfad +) + +require ( + github.com/cosi-project/runtime v0.6.4 + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 + github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475 + github.com/jhump/grpctunnel v0.3.0 + github.com/pensando/goipmi v0.0.0-20240603174436-eb122d901c23 + github.com/pin/tftp/v3 v3.1.0 + github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587 + github.com/siderolabs/gen v0.6.1 + github.com/siderolabs/image-factory v0.5.0 + github.com/siderolabs/omni/client v0.0.0-20241017162757-284e8b5077cc + github.com/siderolabs/talos-metal-agent v0.0.0-20241016074728-46df49991336 + github.com/spf13/cobra v1.8.1 + go.uber.org/zap v1.27.0 + golang.org/x/net v0.30.0 + golang.org/x/sync v0.8.0 + google.golang.org/grpc v1.67.1 + google.golang.org/protobuf v1.35.1 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/ProtonMail/go-crypto v1.1.0-beta.0-proton // indirect + github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect + github.com/ProtonMail/gopenpgp/v2 v2.7.5 // indirect + github.com/adrg/xdg v0.5.1 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cloudflare/circl v1.5.0 // indirect + github.com/containerd/go-cni v1.1.10 // indirect + github.com/containernetworking/cni v1.2.3 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fullstorydev/grpchan v1.1.1 // indirect + github.com/gertd/go-pluralize v0.2.1 // indirect + github.com/google/cel-go v0.21.0 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/native v1.1.0 // indirect + github.com/jsimonetti/rtnetlink/v2 v2.0.2 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/mdlayher/ethtool v0.2.0 // indirect + github.com/mdlayher/genetlink v1.3.2 // indirect + github.com/mdlayher/netlink v1.7.2 // indirect + github.com/mdlayher/socket v0.5.1 // indirect + github.com/opencontainers/runtime-spec v1.2.0 // indirect + github.com/pierrec/lz4/v4 v4.1.21 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/siderolabs/crypto v0.5.0 // indirect + github.com/siderolabs/go-api-signature v0.3.6 // indirect + github.com/siderolabs/go-blockdevice v0.4.8 // indirect + github.com/siderolabs/go-blockdevice/v2 v2.0.3 // indirect + github.com/siderolabs/go-pointer v1.0.0 // indirect + github.com/siderolabs/net v0.4.0 // indirect + github.com/siderolabs/proto-codec v0.1.1 // indirect + github.com/siderolabs/protoenc v0.2.1 // indirect + github.com/siderolabs/talos/pkg/machinery v1.8.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stoewer/go-strcase v1.3.0 // indirect + github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + golang.org/x/time v0.7.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..cccaf2e --- /dev/null +++ b/go.sum @@ -0,0 +1,343 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.1.0-beta.0-proton h1:ZGewsAoeSirbUS5cO8L0FMQA+iSop9xR1nmFYifDBPo= +github.com/ProtonMail/go-crypto v1.1.0-beta.0-proton/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= +github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= +github.com/ProtonMail/gopenpgp/v2 v2.7.5 h1:STOY3vgES59gNgoOt2w0nyHBjKViB/qSg7NjbQWPJkA= +github.com/ProtonMail/gopenpgp/v2 v2.7.5/go.mod h1:IhkNEDaxec6NyzSI0PlxapinnwPVIESk8/76da3Ct3g= +github.com/adrg/xdg v0.5.1 h1:Im8iDbEFARltY09yOJlSGu4Asjk2vF85+3Dyru8uJ0U= +github.com/adrg/xdg v0.5.1/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/brianvoe/gofakeit/v6 v6.24.0 h1:74yq7RRz/noddscZHRS2T84oHZisW9muwbb8sRnU52A= +github.com/brianvoe/gofakeit/v6 v6.24.0/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4= +github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys= +github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/containerd/go-cni v1.1.10 h1:c2U73nld7spSWfiJwSh/8W9DK+/qQwYM2rngIhCyhyg= +github.com/containerd/go-cni v1.1.10/go.mod h1:/Y/sL8yqYQn1ZG1om1OncJB1W4zN3YmjfP/ShCzG/OY= +github.com/containernetworking/cni v1.2.3 h1:hhOcjNVUQTnzdRJ6alC5XF+wd9mfGIUaj8FuJbEslXM= +github.com/containernetworking/cni v1.2.3/go.mod h1:DuLgF+aPd3DzcTQTtp/Nvl1Kim23oFKdm2okJzBQA5M= +github.com/cosi-project/runtime v0.6.4 h1:roifc5e+Q1+72EI36BYSRT9aXyskU+coiKHeoBBWkMg= +github.com/cosi-project/runtime v0.6.4/go.mod h1:EMLs8a55tJ6zA4UyDbRsTvXBd6UIlNwZfCVGvCyiXK8= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= +github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fullstorydev/grpchan v1.1.1 h1:heQqIJlAv5Cnks9a70GRL2EJke6QQoUB25VGR6TZQas= +github.com/fullstorydev/grpchan v1.1.1/go.mod h1:f4HpiV8V6htfY/K44GWV1ESQzHBTq7DinhzqQ95lpgc= +github.com/gertd/go-pluralize v0.2.1 h1:M3uASbVjMnTsPb0PNqg+E/24Vwigyo/tvyMTtAlLgiA= +github.com/gertd/go-pluralize v0.2.1/go.mod h1:rbYaKDbsXxmRfr8uygAEKhOWsjyrrqrkHVpZvoOp8zk= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= +github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475 h1:hxST5pwMBEOWmxpkX20w9oZG+hXdhKmAIPQ3NGGAxas= +github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic= +github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= +github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= +github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ= +github.com/jhump/grpctunnel v0.3.0 h1:itddWDKl7J4CeW4nzY3S/a1s7mPZUb8UtUzEhc/R8mg= +github.com/jhump/grpctunnel v0.3.0/go.mod h1:dn5zls1F+1ftPMkbh4kVTVgGuY5t/v3ZgdjtnSMC3f4= +github.com/jhump/protoreflect v1.11.0 h1:bvACHUD1Ua/3VxY4aAMpItKMhhwbimlKFJKsLsVgDjU= +github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E= +github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/jsimonetti/rtnetlink/v2 v2.0.2 h1:ZKlbCujrIpp4/u3V2Ka0oxlf4BCkt6ojkvpy3nZoCBY= +github.com/jsimonetti/rtnetlink/v2 v2.0.2/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mdlayher/ethtool v0.2.0 h1:akcA4WZVWozzirPASeMq8qgLkxpF3ykftVXwnrMKrhY= +github.com/mdlayher/ethtool v0.2.0/go.mod h1:W0pIBrNPK1TslIN4Z9wt1EVbay66Kbvek2z2f29VBfw= +github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= +github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= +github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= +github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= +github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY= +github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4= +github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= +github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= +github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587 h1:xzZOeCMQLA/W198ZkdVdt4EKFKJtS26B773zNU377ZY= +github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= +github.com/siderolabs/crypto v0.5.0 h1:+Sox0aYLCcD0PAH2cbEcx557zUrONLtuj1Ws+2MFXGc= +github.com/siderolabs/crypto v0.5.0/go.mod h1:hsR3tJ3aaeuhCChsLF4dBd9vlJVPvmhg4vvx2ez4aD4= +github.com/siderolabs/gen v0.5.0 h1:Afdjx+zuZDf53eH5DB+E+T2JeCwBXGinV66A6osLgQI= +github.com/siderolabs/gen v0.5.0/go.mod h1:1GUMBNliW98Xeq8GPQeVMYqQE09LFItE8enR3wgMh3Q= +github.com/siderolabs/gen v0.6.1 h1:Mex6Q41Tlw3e+4cGvlju2x4UwULD5WMo/D82n7IxV0Y= +github.com/siderolabs/gen v0.6.1/go.mod h1:an3a2Y53O7kUjnnK8Bfu3gewtvnIOu5RTU6HalFtXQQ= +github.com/siderolabs/go-api-signature v0.3.6 h1:wDIsXbpl7Oa/FXvxB6uz4VL9INA9fmr3EbmjEZYFJrU= +github.com/siderolabs/go-api-signature v0.3.6/go.mod h1:hoH13AfunHflxbXfh+NoploqV13ZTDfQ1mQJWNVSW9U= +github.com/siderolabs/go-blockdevice v0.4.8 h1:KfdWvIx0Jft5YVuCsFIJFwjWEF1oqtzkgX9PeU9cX4c= +github.com/siderolabs/go-blockdevice v0.4.8/go.mod h1:4PeOuk71pReJj1JQEXDE7kIIQJPVe8a+HZQa+qjxSEA= +github.com/siderolabs/go-blockdevice/v2 v2.0.3 h1:IEgDqd3H3gPphahrdvfAzU8RmD4r5eQdWC+vgFQQoEg= +github.com/siderolabs/go-blockdevice/v2 v2.0.3/go.mod h1:74htzCV913UzaLZ4H+NBXkwWlYnBJIq5m/379ZEcu8w= +github.com/siderolabs/go-pointer v1.0.0 h1:6TshPKep2doDQJAAtHUuHWXbca8ZfyRySjSBT/4GsMU= +github.com/siderolabs/go-pointer v1.0.0/go.mod h1:HTRFUNYa3R+k0FFKNv11zgkaCLzEkWVzoYZ433P3kHc= +github.com/siderolabs/go-retry v0.3.3 h1:zKV+S1vumtO72E6sYsLlmIdV/G/GcYSBLiEx/c9oCEg= +github.com/siderolabs/go-retry v0.3.3/go.mod h1:Ff/VGc7v7un4uQg3DybgrmOWHEmJ8BzZds/XNn/BqMI= +github.com/siderolabs/goipmi v0.0.0-20211214143420-35f956689e67 h1:R22ZIQgXriopn8zTKnya8JWbEEx2AdgTyKL92hxdJoU= +github.com/siderolabs/goipmi v0.0.0-20211214143420-35f956689e67/go.mod h1:Vr1Oadtcem03hG2RUT/dpSQS5md9d6rJ9nA0lUBC91Q= +github.com/siderolabs/image-factory v0.5.0 h1:v1FXZLCcV6xu+6QpgvhDEICxVF7o2VxMjfU0MutkFbo= +github.com/siderolabs/image-factory v0.5.0/go.mod h1:npJwHOBsI+h+gKdezCyrs7ZHDmkgRnrAK2Cjk1nzv8A= +github.com/siderolabs/net v0.4.0 h1:1bOgVay/ijPkJz4qct98nHsiB/ysLQU0KLoBC4qLm7I= +github.com/siderolabs/net v0.4.0/go.mod h1:/ibG+Hm9HU27agp5r9Q3eZicEfjquzNzQNux5uEk0kM= +github.com/siderolabs/proto-codec v0.1.1 h1:4jiUwW/vaXTZ+YNgZDs37B4aj/1mzV/erIkzUUCRY9g= +github.com/siderolabs/proto-codec v0.1.1/go.mod h1:rIvmhKJG8+JwSCGPX+cQljpOMDmuHhLKPkt6KaFwEaU= +github.com/siderolabs/protoenc v0.2.1 h1:BqxEmeWQeMpNP3R6WrPqDatX8sM/r4t97OP8mFmg6GA= +github.com/siderolabs/protoenc v0.2.1/go.mod h1:StTHxjet1g11GpNAWiATgc8K0HMKiFSEVVFOa/H0otc= +github.com/siderolabs/talos/pkg/machinery v1.8.1 h1:oeJQmkLNjEG5jxrzPiC2XMQS5dcg1qZ17p5LKcaCbRM= +github.com/siderolabs/talos/pkg/machinery v1.8.1/go.mod h1:mWTmuUk8G6CdkhUfDmsrIkgPo0G6J5hC/zGazgnyzBg= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a h1:BH1SOPEvehD2kVrndDnGJiUF0TrBpNs+iyYocu6h0og= +github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= +github.com/utkuozdemir/pin-tftp/v3 v3.0.0-20241021135417-0dd7dba351ad h1:l/GaA5Ut8YynZNW0jBhcE6W4aodMPOyURqpza76lhCw= +github.com/utkuozdemir/pin-tftp/v3 v3.0.0-20241021135417-0dd7dba351ad/go.mod h1:Kvfewx0+8GO2KpUCPRZAzVjzVN+TnRgn7tuaPlkf3fw= +github.com/utkuozdemir/sidero-omni/client v0.0.0-20241025225840-6bccefcfa215 h1:8aN9P+6kvmu9TOh6QXhsfO16kSHnrFHgq8v3ts49SWA= +github.com/utkuozdemir/sidero-omni/client v0.0.0-20241025225840-6bccefcfa215/go.mod h1:cGPkF/BZkjXLMqiea1TRhB+/NtGOPBN7MMzZjUh1C+s= +github.com/utkuozdemir/sidero-talos-metal-agent v0.0.0-20241023212532-42f37aaacfad h1:cOAiHiNDTvWceTED0w1leRWs8SQDW3bUEze/4nAvyFQ= +github.com/utkuozdemir/sidero-talos-metal-agent v0.0.0-20241023212532-42f37aaacfad/go.mod h1:Qk/3dPwbnt5LTSeY0telKKmNz7AuoMshqh6EUbA+iBA= +github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= +github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 h1:2oV8dfuIkM1Ti7DwXc0BJfnwr9csz4TDXI9EmiI+Rbw= +google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38/go.mod h1:vuAjtvlwkDKF6L1GQ0SokiRLCGFfeBUXWr/aFFkHACc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/hack/release.sh b/hack/release.sh new file mode 100755 index 0000000..13d9e63 --- /dev/null +++ b/hack/release.sh @@ -0,0 +1,149 @@ +#!/usr/bin/env bash + +# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. +# +# Generated on 2024-10-14T09:32:55Z by kres 34e72ac. + +set -e + +RELEASE_TOOL_IMAGE="ghcr.io/siderolabs/release-tool:latest" + +function release-tool { + docker pull "${RELEASE_TOOL_IMAGE}" >/dev/null + docker run --rm -w /src -v "${PWD}":/src:ro "${RELEASE_TOOL_IMAGE}" -l -d -n -t "${1}" ./hack/release.toml +} + +function changelog { + if [ "$#" -eq 1 ]; then + (release-tool ${1}; echo; cat CHANGELOG.md) > CHANGELOG.md- && mv CHANGELOG.md- CHANGELOG.md + else + echo 1>&2 "Usage: $0 changelog [tag]" + exit 1 + fi +} + +function release-notes { + release-tool "${2}" > "${1}" +} + +function cherry-pick { + if [ $# -ne 2 ]; then + echo 1>&2 "Usage: $0 cherry-pick " + exit 1 + fi + + git checkout $2 + git fetch + git rebase upstream/$2 + git cherry-pick -x $1 +} + +function commit { + if [ $# -ne 1 ]; then + echo 1>&2 "Usage: $0 commit " + exit 1 + fi + + if is_on_main_branch; then + update_license_files + fi + + git commit -s -m "release($1): prepare release" -m "This is the official $1 release." +} + +function is_on_main_branch { + main_remotes=("upstream" "origin") + branch_names=("main" "master") + current_branch=$(git rev-parse --abbrev-ref HEAD) + + echo "Check current branch: $current_branch" + + for remote in "${main_remotes[@]}"; do + echo "Fetch remote $remote..." + + if ! git fetch --quiet "$remote" &>/dev/null; then + echo "Failed to fetch $remote, skip..." + + continue + fi + + for branch_name in "${branch_names[@]}"; do + if ! git rev-parse --verify "$branch_name" &>/dev/null; then + echo "Branch $branch_name does not exist, skip..." + + continue + fi + + echo "Branch $remote/$branch_name exists, comparing..." + + merge_base=$(git merge-base "$current_branch" "$remote/$branch_name") + latest_main=$(git rev-parse "$remote/$branch_name") + + if [ "$merge_base" = "$latest_main" ]; then + echo "Current branch is up-to-date with $remote/$branch_name" + + return 0 + else + echo "Current branch is not on $remote/$branch_name" + + return 1 + fi + done + done + + echo "No main or master branch found on any remote" + + return 1 +} + +function update_license_files { + script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + parent_dir="$(dirname "$script_dir")" + current_year=$(date +"%Y") + change_date=$(date -v+4y +"%Y-%m-%d" 2>/dev/null || date -d "+4 years" +"%Y-%m-%d" 2>/dev/null || date --date="+4 years" +"%Y-%m-%d") + + # Find LICENSE and .kres.yaml files recursively in the parent directory (project root) + find "$parent_dir" \( -name "LICENSE" -o -name ".kres.yaml" \) -type f | while read -r file; do + temp_file="${file}.tmp" + + if [[ $file == *"LICENSE" ]]; then + if grep -q "^Business Source License" "$file"; then + sed -e "s/The Licensed Work is (c) [0-9]\{4\}/The Licensed Work is (c) $current_year/" \ + -e "s/Change Date: [0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}/Change Date: $change_date/" \ + "$file" >"$temp_file" + else + continue # Not a Business Source License file + fi + elif [[ $file == *".kres.yaml" ]]; then + sed -E 's/^([[:space:]]*)ChangeDate:.*$/\1ChangeDate: "'"$change_date"'"/' "$file" >"$temp_file" + fi + + # Check if the file has changed + if ! cmp -s "$file" "$temp_file"; then + mv "$temp_file" "$file" + echo "Updated: $file" + git add "$file" + else + echo "No changes: $file" + rm "$temp_file" + fi + done +} + +if declare -f "$1" > /dev/null +then + cmd="$1" + shift + $cmd "$@" +else + cat < 0 { + // If the client has had iPXE burned into its ROM (or is a VM + // that uses iPXE as the PXE "ROM"), special handling is + // needed because in this mode the client is using iPXE native + // drivers and chainloading to a UNDI stack won't work. + if userClasses[0] == "iPXE" && fwtype == FirmwareX86PC { + fwtype = FirmwareX86Ipxe + } + } + + guid := m.GetOneOption(dhcpv4.OptionClientMachineIdentifier) + switch len(guid) { + case 0: + // A missing GUID is invalid according to the spec, however + // there are PXE ROMs in the wild that omit the GUID and still + // expect to boot. The only thing we do with the GUID is + // mirror it back to the client if it's there, so we might as + // well accept these buggy ROMs. + case 17: + if guid[0] != 0 { + return 0, errors.New("malformed client GUID (option 97), leading byte must be zero") + } + default: + return 0, errors.New("malformed client GUID (option 97), wrong size") + } + + return fwtype, nil +} + +func offerDHCP(req *dhcpv4.DHCPv4, apiAdvertiseAddress string, apiPort int, fwtype Firmware) (*dhcpv4.DHCPv4, error) { + serverIP := net.ParseIP(apiAdvertiseAddress) + ipPort := net.JoinHostPort(serverIP.String(), strconv.Itoa(apiPort)) + + modifiers := []dhcpv4.Modifier{ + dhcpv4.WithServerIP(serverIP), + dhcpv4.WithOptionCopied(req, dhcpv4.OptionClientMachineIdentifier), + dhcpv4.WithOptionCopied(req, dhcpv4.OptionClassIdentifier), + } + + resp, err := dhcpv4.NewReplyFromRequest(req, + modifiers..., + ) + if err != nil { + return nil, err + } + + if resp.GetOneOption(dhcpv4.OptionClassIdentifier) == nil { + resp.UpdateOption(dhcpv4.OptClassIdentifier("PXEClient")) + } + + switch fwtype { + case FirmwareX86PC: + // This is completely standard PXE: just load a file from TFTP. + resp.UpdateOption(dhcpv4.OptTFTPServerName(serverIP.String())) + resp.UpdateOption(dhcpv4.OptBootFileName("undionly.kpxe")) + case FirmwareX86Ipxe: + // Almost standard PXE, but the boot filename needs to be a URL. + resp.UpdateOption(dhcpv4.OptBootFileName(fmt.Sprintf("tftp://%s/undionly.kpxe", serverIP))) + case FirmwareX86EFI: + // This is completely standard PXE: just load a file from TFTP. + resp.UpdateOption(dhcpv4.OptTFTPServerName(serverIP.String())) + resp.UpdateOption(dhcpv4.OptBootFileName("snp.efi")) + case FirmwareARMEFI: + // This is completely standard PXE: just load a file from TFTP. + resp.UpdateOption(dhcpv4.OptTFTPServerName(serverIP.String())) + resp.UpdateOption(dhcpv4.OptBootFileName("snp-arm64.efi")) + case FirmwareX86HTTP: + // This is completely standard HTTP-boot: just load a file from HTTP. + resp.UpdateOption(dhcpv4.OptBootFileName(fmt.Sprintf("http://%s/tftp/snp.efi", ipPort))) + case FirmwareARMHTTP: + // This is completely standard HTTP-boot: just load a file from HTTP. + resp.UpdateOption(dhcpv4.OptBootFileName(fmt.Sprintf("http://%s/tftp/snp-arm64.efi", ipPort))) + case FirmwareUnsupported: + fallthrough + default: + return nil, fmt.Errorf("unsupported firmware type %d", fwtype) + } + + return resp, nil +} + +// Firmware describes a kind of firmware attempting to boot. +// +// This should only be used for selecting the right bootloader, +// kernel selection should key off the more generic architecture. +type Firmware int + +// The bootloaders that we know how to handle. +const ( + FirmwareUnsupported Firmware = iota // Unsupported + FirmwareX86PC // "Classic" x86 BIOS with PXE/UNDI support + FirmwareX86EFI // EFI x86 + FirmwareARMEFI // EFI ARM64 + FirmwareX86Ipxe // "Classic" x86 BIOS running iPXE (no UNDI support) + FirmwareX86HTTP // HTTP Boot X86 + FirmwareARMHTTP // ARM64 HTTP Boot +) diff --git a/internal/imagefactory/imagefactory.go b/internal/imagefactory/imagefactory.go new file mode 100644 index 0000000..ec148fe --- /dev/null +++ b/internal/imagefactory/imagefactory.go @@ -0,0 +1,128 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package imagefactory provides an abstraction to the image factory for the bare metal infra provider. +package imagefactory + +import ( + "context" + "fmt" + "net" + "strconv" + "strings" + "time" + + "github.com/siderolabs/image-factory/pkg/client" + "github.com/siderolabs/image-factory/pkg/schematic" + "github.com/siderolabs/omni/client/pkg/meta" + "gopkg.in/yaml.v3" +) + +const metalAgentModeExtension = "siderolabs/metal-agent-mode" + +// Client is an image factory client. +type Client struct { + pxeBaseURL string + talosVersion string + machineLabelsMeta string + factoryClient *client.Client + talosConfigURL string +} + +// NewClient creates a new image factory client. +func NewClient(baseURL, pxeBaseURL, talosVersion, apiAdvertiseAddress string, apiPort int, machineLabels []string) (*Client, error) { + labelsMeta, err := parseLabels(machineLabels) + if err != nil { + return nil, err + } + + factoryClient, err := client.New(baseURL) + if err != nil { + return nil, err + } + + talosConfigURL := fmt.Sprintf("https://%s/config?u={uuid}", net.JoinHostPort(apiAdvertiseAddress, strconv.Itoa(apiPort))) + + return &Client{ + pxeBaseURL: pxeBaseURL, + talosVersion: talosVersion, + machineLabelsMeta: labelsMeta, + talosConfigURL: talosConfigURL, + + factoryClient: factoryClient, + }, nil +} + +// SchematicIPXEURL ensures a schematic exists on the image factory and returns the iPXE URL to it. +// +// If agentMode is true, the schematic will be created with the metal-agent-mode extension. +func (c *Client) SchematicIPXEURL(ctx context.Context, agentMode bool) (string, error) { + var ( + metaValues []schematic.MetaValue + officialExtensions []string + ) + + if c.machineLabelsMeta != "" { + metaValues = append(metaValues, schematic.MetaValue{ + Key: meta.LabelsMeta, + Value: c.machineLabelsMeta, + }) + } + + if agentMode { + officialExtensions = append(officialExtensions, metalAgentModeExtension) + } + + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + + sch := schematic.Schematic{ + Customization: schematic.Customization{ + ExtraKernelArgs: []string{ + "talos.config=" + c.talosConfigURL, + }, + Meta: metaValues, + SystemExtensions: schematic.SystemExtensions{ + OfficialExtensions: officialExtensions, + }, + }, + } + + schematicID, err := c.factoryClient.SchematicCreate(ctx, sch) + if err != nil { + return "", fmt.Errorf("failed to create schematic: %w", err) + } + + ipxeURL := fmt.Sprintf("https://%s/pxe/%s/%s/metal-amd64", c.pxeBaseURL, schematicID, c.talosVersion) + + return ipxeURL, err +} + +func parseLabels(machineLabels []string) (string, error) { + labels := map[string]string{} + + for _, l := range machineLabels { + parts := strings.Split(l, "=") + if len(parts) > 2 { + return "", fmt.Errorf("malformed label %s", l) + } + + value := "" + + if len(parts) > 1 { + value = parts[1] + } + + labels[parts[0]] = value + } + + data, err := yaml.Marshal(meta.ImageLabels{ + Labels: labels, + }) + if err != nil { + return "", err + } + + return string(data), nil +} diff --git a/internal/ip/ip.go b/internal/ip/ip.go new file mode 100644 index 0000000..f3cfe65 --- /dev/null +++ b/internal/ip/ip.go @@ -0,0 +1,51 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package ip provides IP address related functionality. +package ip + +import ( + "fmt" + "net" +) + +// RoutableIPs returns a list of routable IP addresses. +func RoutableIPs() ([]string, error) { + addresses, err := net.InterfaceAddrs() + if err != nil { + return nil, fmt.Errorf("failed to get interfaces: %w", err) + } + + routableIPs := make([]string, 0, len(addresses)) + + for _, addr := range addresses { + ipNet, ok := addr.(*net.IPNet) + if !ok { + continue + } + + if isRoutableIP(ipNet.IP) { + routableIPs = append(routableIPs, ipNet.IP.String()) + } + } + + return routableIPs, nil +} + +func isRoutableIP(ip net.IP) bool { + isReservedIPv4 := func(ip net.IP) bool { + return ip[0] >= 240 + } + + if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() || + ip.IsMulticast() || ip.IsUnspecified() { + return false + } + + if ip.To4() != nil { + return !isReservedIPv4(ip) + } + + return true +} diff --git a/internal/ipxe/ipxe.go b/internal/ipxe/ipxe.go new file mode 100644 index 0000000..63d5aba --- /dev/null +++ b/internal/ipxe/ipxe.go @@ -0,0 +1,137 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package ipxe provides iPXE functionality. +package ipxe + +import ( + "context" + "fmt" + "net/http" + + "github.com/cosi-project/runtime/pkg/state" + "go.uber.org/zap" + + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/resources" +) + +const ipxeScriptTemplateFormat = `#!ipxe +chain --replace %s +` + +// OmniClient represents an Omni client. +type OmniClient interface { + GetMachine(ctx context.Context, id string) (*resources.Machine, error) +} + +// ImageFactoryClient represents an image factory client which ensures a schematic exists on image factory, and returns the PXE URL to it. +type ImageFactoryClient interface { + SchematicIPXEURL(ctx context.Context, agentMode bool) (string, error) +} + +// Handler represents an iPXE handler. +type Handler struct { + omniClient OmniClient + imageFactoryClient ImageFactoryClient + + logger *zap.Logger + + agentTalosImage string +} + +// ServeHTTP serves the iPXE request. +// +// URL pattern: http://ip-of-this-provider:50042/ipxe?uuid=${uuid}&mac=${net${idx}/mac:hexhyp}&domain=${domain}&hostname=${hostname}&serial=${serial}&arch=${buildarch} +// +// Implements http.Handler interface. +func (handler *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + uuid := req.URL.Query().Get("uuid") + mac := req.URL.Query().Get("mac") + domain := req.URL.Query().Get("domain") + hostname := req.URL.Query().Get("hostname") + serial := req.URL.Query().Get("serial") + arch := req.URL.Query().Get("arch") + + handler.logger.Info("handle iPXE request", zap.String("uuid", uuid), zap.String("mac", mac), + zap.String("domain", domain), zap.String("hostname", hostname), zap.String("serial", serial), zap.String("arch", arch)) + + // decide if we need to boot the machine into the agent mode + _, err := handler.omniClient.GetMachine(req.Context(), uuid) + if err != nil { + if !state.IsNotFoundError(err) { + handler.logger.Error("failed to get machine", zap.Error(err)) + + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("failed to get machine")) //nolint:errcheck + + return + } + + useImageFactory := handler.agentTalosImage == "" + + if useImageFactory { + handler.serveFactoryIPXEScript(w, req, true) + + return + } + + // todo: boot with local assets in the agent talos image + + return + } + + // boot into regular Talos over the image factory + + ipxeURL, schematicErr := handler.imageFactoryClient.SchematicIPXEURL(req.Context(), false) + if schematicErr != nil { + handler.logger.Error("failed to get schematic IPXE URL", zap.Error(schematicErr)) + + w.WriteHeader(http.StatusInternalServerError) + + w.Write([]byte("failed to get schematic IPXE URL")) //nolint:errcheck + + return + } + + ipxeScript := fmt.Sprintf(ipxeScriptTemplateFormat, ipxeURL) + + w.WriteHeader(http.StatusOK) + w.Write([]byte(ipxeScript)) //nolint:errcheck +} + +func (handler *Handler) serveFactoryIPXEScript(w http.ResponseWriter, req *http.Request, agentMode bool) { + ipxeURL, schematicErr := handler.imageFactoryClient.SchematicIPXEURL(req.Context(), agentMode) + if schematicErr != nil { + handler.logger.Error("failed to get schematic IPXE URL", zap.Error(schematicErr)) + + w.WriteHeader(http.StatusInternalServerError) + + w.Write([]byte("failed to get schematic IPXE URL")) //nolint:errcheck + + return + } + + ipxeScript := fmt.Sprintf(ipxeScriptTemplateFormat, ipxeURL) + + w.WriteHeader(http.StatusOK) + w.Write([]byte(ipxeScript)) //nolint:errcheck +} + +// NewHandler creates a new iPXE server. +func NewHandler(imageFactoryClient ImageFactoryClient, omniClient OmniClient, endpoint string, port int, agentTalosImage string, logger *zap.Logger) (*Handler, error) { + logger.Info("patch iPXE binaries") + + if err := patchBinaries(endpoint, port); err != nil { + return nil, err + } + + logger.Info("successfully patched iPXE binaries") + + return &Handler{ + omniClient: omniClient, + imageFactoryClient: imageFactoryClient, + agentTalosImage: agentTalosImage, + logger: logger, + }, nil +} diff --git a/internal/ipxe/patch.go b/internal/ipxe/patch.go new file mode 100644 index 0000000..1ea7bd4 --- /dev/null +++ b/internal/ipxe/patch.go @@ -0,0 +1,205 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package ipxe + +import ( + "bytes" + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "text/template" + + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/constants" +) + +// bootTemplate is embedded into iPXE binary when that binary is sent to the node. +// +//nolint:dupword +var bootTemplate = template.Must(template.New("iPXE embedded").Parse(`#!ipxe +prompt --key 0x02 --timeout 2000 Press Ctrl-B for the iPXE command line... && shell || + +{{/* print interfaces */}} +ifstat + +{{/* retry 10 times overall */}} +set attempts:int32 10 +set x:int32 0 + +:retry_loop + + set idx:int32 0 + + :loop + {{/* try DHCP on each interface */}} + isset ${net${idx}/mac} || goto exhausted + + ifclose + iflinkwait --timeout 5000 net${idx} || goto next_iface + dhcp net${idx} || goto next_iface + goto boot + + :next_iface + inc idx && goto loop + + :boot + {{/* attempt boot, if fails try next iface */}} + route + + chain --replace http://{{ .Endpoint }}:{{ .Port }}/ipxe?uuid=${uuid}&mac=${net${idx}/mac:hexhyp}&domain=${domain}&hostname=${hostname}&serial=${serial}&arch=${buildarch} || goto next_iface + +:exhausted + echo + echo Failed to iPXE boot successfully via all interfaces + + iseq ${x} ${attempts} && goto fail || + + echo Retrying... + echo + + inc x + goto retry_loop + +:fail + echo + echo Failed to get a valid response after ${attempts} attempts + echo + + echo Rebooting in 5 seconds... + sleep 5 + reboot +`)) + +func buildBootScript(endpoint string, port int) ([]byte, error) { + var buf bytes.Buffer + + if err := bootTemplate.Execute(&buf, struct { + Endpoint string + Port int + }{ + Endpoint: endpoint, + Port: port, + }); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +// patchBinaries patches iPXE binaries on the fly with the new embedded script. +// +// This relies on special build in `pkgs/ipxe` where a placeholder iPXE script is embedded. +// EFI iPXE binaries are uncompressed, so these are patched directly. +// BIOS amd64 undionly.pxe is compressed, so we instead patch uncompressed version and compress it back using zbin. +// (zbin is built with iPXE). +func patchBinaries(endpoint string, port int) error { + bootScript, err := buildBootScript(endpoint, port) + if err != nil { + return fmt.Errorf("failed to build boot script: %w", err) + } + + for _, name := range []string{"ipxe", "snp"} { + if err = patchScript( + fmt.Sprintf(constants.IPXEPath+"/amd64/%s.efi", name), + fmt.Sprintf(constants.TFTPPath+"/%s.efi", name), + bootScript, + ); err != nil { + return fmt.Errorf("failed to patch %q: %w", name, err) + } + + if err = patchScript( + fmt.Sprintf(constants.IPXEPath+"/arm64/%s.efi", name), + fmt.Sprintf(constants.TFTPPath+"/%s-arm64.efi", name), + bootScript, + ); err != nil { + return fmt.Errorf("failed to patch %q: %w", name, err) + } + } + + if err = patchScript(constants.IPXEPath+"/amd64/kpxe/undionly.kpxe.bin", constants.IPXEPath+"/amd64/kpxe/undionly.kpxe.bin.patched", bootScript); err != nil { + return fmt.Errorf("failed to patch undionly.kpxe.bin: %w", err) + } + + if err = compressKPXE(constants.IPXEPath+"/amd64/kpxe/undionly.kpxe.bin.patched", constants.IPXEPath+"/amd64/kpxe/undionly.kpxe.zinfo", constants.TFTPPath+"/undionly.kpxe"); err != nil { + return fmt.Errorf("failed to compress undionly.kpxe: %w", err) + } + + if err = compressKPXE(constants.IPXEPath+"/amd64/kpxe/undionly.kpxe.bin.patched", constants.IPXEPath+"/amd64/kpxe/undionly.kpxe.zinfo", constants.TFTPPath+"/undionly.kpxe.0"); err != nil { + return fmt.Errorf("failed to compress undionly.kpxe.0: %w", err) + } + + return nil +} + +var ( + placeholderStart = []byte("# *PLACEHOLDER START*") + placeholderEnd = []byte("# *PLACEHOLDER END*") +) + +func patchScript(source, destination string, script []byte) error { + contents, err := os.ReadFile(source) + if err != nil { + return err + } + + start := bytes.Index(contents, placeholderStart) + if start == -1 { + return fmt.Errorf("placeholder start not found in %q", source) + } + + end := bytes.Index(contents, placeholderEnd) + if end == -1 { + return fmt.Errorf("placeholder end not found in %q", source) + } + + if end < start { + return fmt.Errorf("placeholder end before start") + } + + end += len(placeholderEnd) + + length := end - start + + if len(script) > length { + return fmt.Errorf("script size %d is larger than placeholder space %d", len(script), length) + } + + script = append(script, bytes.Repeat([]byte{'\n'}, length-len(script))...) + + copy(contents[start:end], script) + + if err = os.MkdirAll(filepath.Dir(destination), 0o755); err != nil { + return err + } + + return os.WriteFile(destination, contents, 0o644) +} + +// compressKPXE is equivalent to: ./util/zbin bin/undionly.kpxe.bin bin/undionly.kpxe.zinfo > bin/undionly.kpxe.zbin. +func compressKPXE(binFile, infoFile, outFile string) error { + out, err := os.Create(outFile) + if err != nil { + return err + } + + defer out.Close() //nolint:errcheck + + cmd := exec.Command("/bin/zbin", binFile, infoFile) + cmd.Stdout = out + + err = cmd.Run() + if err != nil { + var exitErr *exec.ExitError + + if errors.As(err, &exitErr) { + return fmt.Errorf("zbin failed with exit code %d, stderr: %v", exitErr.ExitCode(), string(exitErr.Stderr)) + } + + return fmt.Errorf("failed to run zbin: %w", err) + } + + return nil +} diff --git a/internal/meta/meta.go b/internal/meta/meta.go new file mode 100644 index 0000000..c8bf8f0 --- /dev/null +++ b/internal/meta/meta.go @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package meta contains meta information about the provider. +package meta + +// ProviderID is the ID of the provider. +var ProviderID = "bare-metal" diff --git a/internal/omni/omni.go b/internal/omni/omni.go new file mode 100644 index 0000000..1fbd193 --- /dev/null +++ b/internal/omni/omni.go @@ -0,0 +1,188 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package omni provides Omni-related functionality. +package omni + +import ( + "context" + "encoding/base64" + "fmt" + "os" + + "github.com/cosi-project/runtime/pkg/safe" + "github.com/cosi-project/runtime/pkg/state" + "github.com/siderolabs/omni/client/pkg/client" + "github.com/siderolabs/omni/client/pkg/jointoken" + "github.com/siderolabs/omni/client/pkg/omni/resources/infra" + "github.com/siderolabs/omni/client/pkg/omni/resources/omni" + "github.com/siderolabs/omni/client/pkg/omni/resources/siderolink" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + providerpb "github.com/siderolabs/omni-infra-provider-bare-metal/api/provider" + "github.com/siderolabs/omni-infra-provider-bare-metal/api/specs" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/meta" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/resources" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/service" +) + +// Client is a wrapper around the Omni client. +type Client struct { + omniClient *client.Client +} + +// BuildClient creates a new Omni client. +func BuildClient(endpoint string, insecureSkipTLSVerify bool) (*Client, error) { + serviceAccountKey := os.Getenv("OMNI_SERVICE_ACCOUNT_KEY") + + cliOpts := []client.Option{ + client.WithInsecureSkipTLSVerify(insecureSkipTLSVerify), + } + + if serviceAccountKey != "" { + cliOpts = append(cliOpts, client.WithServiceAccount(serviceAccountKey)) + } + + omniClient, err := client.New(endpoint, cliOpts...) + if err != nil { + return nil, fmt.Errorf("failed to create Omni client: %w", err) + } + + return &Client{omniClient: omniClient}, nil +} + +// Close closes the Omni client. +func (c *Client) Close() error { + return c.omniClient.Close() +} + +// GetSiderolinkAPIURL returns the SideroLink API URL. +func (c *Client) GetSiderolinkAPIURL(ctx context.Context) (string, error) { + st := c.omniClient.Omni().State() + + connectionParams, err := safe.StateGetByID[*siderolink.ConnectionParams](ctx, st, siderolink.ConfigID) + if err != nil { + return "", fmt.Errorf("failed to get connection params: %w", err) + } + + token, err := jointoken.NewWithExtraData(connectionParams.TypedSpec().Value.JoinToken, map[string]string{ + omni.LabelInfraProviderID: meta.ProviderID, // go to omni, don't do the check of MachineReqStatus + }) + if err != nil { + return "", err + } + + tokenString, err := token.Encode() + if err != nil { + return "", fmt.Errorf("failed to encode the siderolink token: %w", err) + } + + apiURL, err := siderolink.APIURL(connectionParams, siderolink.WithJoinToken(tokenString)) + if err != nil { + return "", fmt.Errorf("failed to build API URL: %w", err) + } + + return apiURL, nil +} + +// EnsureProviderStatus makes sure that the infra.ProviderStatus resource exists and is up to date for this provider. +func (c *Client) EnsureProviderStatus(ctx context.Context, name, description string, rawIcon []byte) error { + populate := func(res *infra.ProviderStatus) { + res.Metadata().Labels().Set(omni.LabelIsStaticInfraProvider, "") + + res.TypedSpec().Value.Name = name + res.TypedSpec().Value.Description = description + res.TypedSpec().Value.Icon = base64.RawStdEncoding.EncodeToString(rawIcon) + } + + providerStatus := infra.NewProviderStatus(meta.ProviderID) + + populate(providerStatus) + + st := c.omniClient.Omni().State() + + if err := st.Create(ctx, providerStatus); err != nil { + if !state.IsConflictError(err) { + return err + } + + if _, err = safe.StateUpdateWithConflicts(ctx, st, providerStatus.Metadata(), func(res *infra.ProviderStatus) error { + populate(res) + + return nil + }); err != nil { + return err + } + } + + return nil +} + +// RunReverseTunnel starts the reverse GRPC tunnel to Omni. +func (c *Client) RunReverseTunnel(ctx context.Context, agentController service.AgentController, logger *zap.Logger) error { + reverseTunnelServer := c.omniClient.Tunnel() + providerServiceServer := service.NewProviderServiceServer(agentController, c, logger) + + providerpb.RegisterProviderServiceServer(reverseTunnelServer, providerServiceServer) + + // Open the reverse tunnel and serve requests. + if _, err := reverseTunnelServer.Serve(ctx); err != nil { + if status.Code(err) == codes.Canceled { + return nil + } + + return fmt.Errorf("failed to serve reverse tunnel: %w", err) + } + + return nil +} + +// GetMachine returns the machine with the given ID from the persistent state. +func (c *Client) GetMachine(ctx context.Context, id string) (*resources.Machine, error) { + machine, err := safe.StateGetByID[*resources.Machine](ctx, c.omniClient.Omni().State(), id) + if err != nil { + return nil, err + } + + return machine, nil +} + +// SaveMachine saves the machine with the given ID and spec to the persistent state. +func (c *Client) SaveMachine(ctx context.Context, id string, spec *specs.MachineSpec) (*resources.Machine, error) { + st := c.omniClient.Omni().State() + machine := resources.NewMachine(id) + machine.TypedSpec().Value = spec + + if err := st.Create(ctx, machine); err != nil { + if !state.IsConflictError(err) { + return nil, err + } + + machine, err = safe.StateUpdateWithConflicts(ctx, st, machine.Metadata(), func(res *resources.Machine) error { + res.TypedSpec().Value = spec + + return nil + }) + if err != nil { + return nil, err + } + } + + return machine, nil +} + +// RemoveMachine removes the machine from the persistent state with the given ID. +func (c *Client) RemoveMachine(ctx context.Context, id string) error { + st := c.omniClient.Omni().State() + + if err := st.Destroy(ctx, resources.NewMachine(id).Metadata()); err != nil { + if !state.IsNotFoundError(err) { + return err + } + } + + return nil +} diff --git a/internal/provider/data/icon.svg b/internal/provider/data/icon.svg new file mode 100644 index 0000000..74fc065 --- /dev/null +++ b/internal/provider/data/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/internal/provider/provider.go b/internal/provider/provider.go new file mode 100644 index 0000000..6584a28 --- /dev/null +++ b/internal/provider/provider.go @@ -0,0 +1,135 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package provider implements the bare metal infra provider. +package provider + +import ( + "context" + _ "embed" + "fmt" + + "go.uber.org/zap" + "golang.org/x/sync/errgroup" + + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/agent" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/config" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/dhcp" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/imagefactory" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/ipxe" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/omni" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/server" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/tftp" +) + +//go:embed data/icon.svg +var icon []byte + +// Options contains the provider options. +type Options struct { + Name string + Description string + OmniAPIEndpoint string + AgentTalosImage string + ImageFactoryBaseURL string + ImageFactoryPXEBaseURL string + ImageFactoryTalosVersion string + APIListenAddress string + APIAdvertiseAddress string + MachineLabels []string + APIPort int + InsecureSkipTLSVerify bool +} + +// Provider implements the bare metal infra provider. +type Provider struct { + logger *zap.Logger + + options Options +} + +// New creates a new Provider. +func New(options Options, logger *zap.Logger) *Provider { + return &Provider{ + options: options, + logger: logger, + } +} + +// Run runs the provider. +func (p *Provider) Run(ctx context.Context) error { + omniClient, err := omni.BuildClient(p.options.OmniAPIEndpoint, p.options.InsecureSkipTLSVerify) + if err != nil { + return fmt.Errorf("failed to build omni client: %w", err) + } + + defer omniClient.Close() //nolint:errcheck + + if err = omniClient.EnsureProviderStatus(ctx, p.options.Name, p.options.Description, icon); err != nil { + return fmt.Errorf("failed to create/update provider status: %w", err) + } + + imageFactoryClient, err := imagefactory.NewClient(p.options.ImageFactoryBaseURL, p.options.ImageFactoryPXEBaseURL, + p.options.ImageFactoryTalosVersion, p.options.APIAdvertiseAddress, p.options.APIPort, p.options.MachineLabels) + if err != nil { + return fmt.Errorf("failed to create image factory client: %w", err) + } + + ipxeHandler, err := ipxe.NewHandler(imageFactoryClient, omniClient, p.options.APIAdvertiseAddress, p.options.APIPort, + p.options.AgentTalosImage, p.logger.With(zap.String("component", "ipxe_handler"))) + if err != nil { + return fmt.Errorf("failed to create iPXE handler: %w", err) + } + + configHandler, err := config.NewHandler(ctx, omniClient, p.logger.With(zap.String("component", "config_handler"))) + if err != nil { + return fmt.Errorf("failed to create config handler: %w", err) + } + + srvr := server.New(ctx, p.options.APIListenAddress, p.options.APIPort, configHandler, ipxeHandler, p.logger.With(zap.String("component", "server"))) + agentController := agent.NewController(srvr, p.logger.With(zap.String("component", "controller"))) + dhcpProxy := dhcp.NewProxy(p.options.APIAdvertiseAddress, p.options.APIPort, p.logger.With(zap.String("component", "dhcp_proxy"))) + tftpServer := tftp.NewServer(p.logger.With(zap.String("component", "tftp_server"))) + + eg, ctx := errgroup.WithContext(ctx) + + eg.Go(p.runComponent("server", func() error { + return srvr.Run(ctx) + })) + + eg.Go(p.runComponent("reverse tunnel", func() error { + return omniClient.RunReverseTunnel(ctx, agentController, p.logger.With(zap.String("component", "reverse_tunnel"))) + })) + + eg.Go(p.runComponent("DHCP proxy", func() error { + return dhcpProxy.Run(ctx) + })) + + eg.Go(p.runComponent("TFTP server", func() error { + return tftpServer.Run(ctx) + })) + + if err = eg.Wait(); err != nil { + return fmt.Errorf("failed to run provider: %w", err) + } + + return nil +} + +func (p *Provider) runComponent(name string, f func() error) func() error { + return func() error { + p.logger.Info("start component ", zap.String("name", name)) + + err := f() + if err != nil { + p.logger.Error("failed to run component", zap.String("name", name), zap.Error(err)) + + return err + } + + p.logger.Info("component stopped", zap.String("name", name)) + + return nil + } +} diff --git a/internal/resources/machine.go b/internal/resources/machine.go new file mode 100644 index 0000000..ae95b53 --- /dev/null +++ b/internal/resources/machine.go @@ -0,0 +1,49 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package resources contains resources stored in the KubeVirt infra provider state. +package resources + +import ( + "github.com/cosi-project/runtime/pkg/resource" + "github.com/cosi-project/runtime/pkg/resource/meta" + "github.com/cosi-project/runtime/pkg/resource/protobuf" + "github.com/cosi-project/runtime/pkg/resource/typed" + "github.com/siderolabs/omni/client/pkg/infra" + + "github.com/siderolabs/omni-infra-provider-bare-metal/api/specs" + providermeta "github.com/siderolabs/omni-infra-provider-bare-metal/internal/meta" +) + +var namespace = infra.ResourceNamespace(providermeta.ProviderID) + +// NewMachine creates new Machine. +func NewMachine(id string) *Machine { + return typed.NewResource[MachineSpec, MachineExtension]( + resource.NewMetadata(namespace, MachineType, id, resource.VersionUndefined), + protobuf.NewResourceSpec(&specs.MachineSpec{}), + ) +} + +// MachineType is the type of Machine resource. +var MachineType = infra.ResourceType("Machine", providermeta.ProviderID) + +// Machine describes fake machine configuration. +type Machine = typed.Resource[MachineSpec, MachineExtension] + +// MachineSpec wraps specs.MachineSpec. +type MachineSpec = protobuf.ResourceSpec[specs.MachineSpec, *specs.MachineSpec] + +// MachineExtension providers auxiliary methods for Machine resource. +type MachineExtension struct{} + +// ResourceDefinition implements [typed.Extension] interface. +func (MachineExtension) ResourceDefinition() meta.ResourceDefinitionSpec { + return meta.ResourceDefinitionSpec{ + Type: MachineType, + Aliases: []resource.Type{}, + DefaultNamespace: namespace, + PrintColumns: []meta.PrintColumn{}, + } +} diff --git a/internal/server/server.go b/internal/server/server.go new file mode 100644 index 0000000..3c5c1fc --- /dev/null +++ b/internal/server/server.go @@ -0,0 +1,132 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package server implements the HTTP and GRPC servers. +package server + +import ( + "context" + "errors" + "fmt" + "net" + "net/http" + "strconv" + "strings" + "time" + + "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/recovery" + "go.uber.org/zap" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" + "golang.org/x/sync/errgroup" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/status" +) + +// Server represents the HTTP and GRPC servers. +type Server struct { + grpcServer *grpc.Server + httpServer *http.Server +} + +// RegisterService registers a service with the GRPC server. +// +// Implements grpc.ServiceRegistrar interface. +func (s *Server) RegisterService(desc *grpc.ServiceDesc, impl any) { + s.grpcServer.RegisterService(desc, impl) +} + +// New creates a new server. +func New(ctx context.Context, endpoint string, port int, configHandler, ipxeHandler http.Handler, logger *zap.Logger) *Server { + recoveryOption := recovery.WithRecoveryHandler(recoveryHandler(logger)) + + grpcServer := grpc.NewServer( + grpc.ChainUnaryInterceptor(recovery.UnaryServerInterceptor(recoveryOption)), + grpc.ChainStreamInterceptor(recovery.StreamServerInterceptor(recoveryOption)), + grpc.Creds(insecure.NewCredentials()), + ) + + httpServer := &http.Server{ + Addr: net.JoinHostPort(endpoint, strconv.Itoa(port)), + Handler: newMultiHandler(configHandler, ipxeHandler, grpcServer), + BaseContext: func(net.Listener) context.Context { + return ctx + }, + } + + return &Server{ + grpcServer: grpcServer, + httpServer: httpServer, + } +} + +// Run runs the server. +func (s *Server) Run(ctx context.Context) error { + eg, ctx := errgroup.WithContext(ctx) + + eg.Go(func() error { + <-ctx.Done() + + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + if err := s.httpServer.Shutdown(shutdownCtx); err != nil { //nolint:contextcheck + return fmt.Errorf("failed to shutdown iPXE server: %w", err) + } + + return nil + }) + + eg.Go(func() error { + if err := s.httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + return fmt.Errorf("failed to run server: %w", err) + } + + return nil + }) + + return eg.Wait() +} + +func newMultiHandler(configHandler, ipxeHandler http.Handler, grpcHandler http.Handler) http.Handler { + mux := http.NewServeMux() + + mux.Handle("/config", configHandler) + mux.Handle("/ipxe", ipxeHandler) + + multi := &multiHandler{ + httpHandler: mux, + grpcHandler: grpcHandler, + } + + return h2c.NewHandler(multi, &http2.Server{}) +} + +type multiHandler struct { + httpHandler http.Handler + grpcHandler http.Handler +} + +func (m *multiHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + if req.ProtoMajor == 2 && strings.HasPrefix( + req.Header.Get("Content-Type"), "application/grpc") { + m.grpcHandler.ServeHTTP(w, req) + + return + } + + m.httpHandler.ServeHTTP(w, req) +} + +func recoveryHandler(logger *zap.Logger) recovery.RecoveryHandlerFunc { + return func(p any) error { + if logger != nil { + logger.Error("grpc panic", zap.Any("panic", p), zap.Stack("stack")) + } + + return status.Errorf(codes.Internal, "%v", p) + } +} diff --git a/internal/service/service.go b/internal/service/service.go new file mode 100644 index 0000000..e58455b --- /dev/null +++ b/internal/service/service.go @@ -0,0 +1,144 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package service implements the bare metal infra provider GRPC service server. +package service + +import ( + "context" + + goipmi "github.com/pensando/goipmi" + "go.uber.org/zap" + + "github.com/siderolabs/omni-infra-provider-bare-metal/api/provider" + "github.com/siderolabs/omni-infra-provider-bare-metal/api/specs" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/resources" +) + +const ipmiUsername = "talos-agent" + +// OmniClient is the interface to manage persisted resources.Machine resources. +type OmniClient interface { + GetMachine(ctx context.Context, id string) (*resources.Machine, error) + SaveMachine(ctx context.Context, id string, spec *specs.MachineSpec) (*resources.Machine, error) + RemoveMachine(ctx context.Context, id string) error +} + +// AgentController is the interface to send commands to the Talos metal agent. +type AgentController interface { + SetIPMICredentials(ctx context.Context, id, username string) (string, error) + GetIPMIInfo(ctx context.Context, id string) (string, int, error) +} + +// ProviderServiceServer is the bare metal infra provider service server. +type ProviderServiceServer struct { + providerpb.UnimplementedProviderServiceServer + + logger *zap.Logger + agentController AgentController + omniClient OmniClient +} + +// NewProviderServiceServer creates a new ProviderServiceServer. +func NewProviderServiceServer(agentController AgentController, omniClient OmniClient, logger *zap.Logger) *ProviderServiceServer { + return &ProviderServiceServer{ + agentController: agentController, + omniClient: omniClient, + logger: logger, + } +} + +// ProvisionMachine provisions a machine. +func (p *ProviderServiceServer) ProvisionMachine(ctx context.Context, request *providerpb.ProvisionMachineRequest) (*providerpb.ProvisionMachineResponse, error) { + p.logger.Info("provision machine", zap.String("machine_id", request.Id)) + + password, err := p.agentController.SetIPMICredentials(ctx, request.Id, ipmiUsername) + if err != nil { + return nil, err + } + + ipmiIP, ipmiPort, err := p.agentController.GetIPMIInfo(ctx, request.Id) + if err != nil { + return nil, err + } + + machineSpec := &specs.MachineSpec{ + IpmiIp: ipmiIP, + IpmiPort: uint32(ipmiPort), + IpmiPassword: password, + } + + machine, err := p.omniClient.SaveMachine(ctx, request.Id, machineSpec) + if err != nil { + return nil, err + } + + if err = withClient(machine, func(client *goipmi.Client) error { + return client.Control(goipmi.ControlPowerUp) + }); err != nil { + return nil, err + } + + return &providerpb.ProvisionMachineResponse{}, nil +} + +// DeprovisionMachine deprovisions a machine. +func (p *ProviderServiceServer) DeprovisionMachine(ctx context.Context, request *providerpb.DeprovisionMachineRequest) (*providerpb.DeprovisionMachineResponse, error) { + p.logger.Info("deprovision machine", zap.String("machine_id", request.Id)) + + machine, err := p.omniClient.GetMachine(ctx, request.Id) + if err != nil { + return nil, err + } + + // remove the machine resource, then reboot, so we boot back into the agent mode + if err = p.omniClient.RemoveMachine(ctx, request.Id); err != nil { + return nil, err + } + + if err = withClient(machine, func(client *goipmi.Client) error { + return client.Control(goipmi.ControlPowerCycle) + }); err != nil { + return nil, err + } + + return &providerpb.DeprovisionMachineResponse{}, nil +} + +// PowerOnMachine powers on a machine. +func (p *ProviderServiceServer) PowerOnMachine(ctx context.Context, request *providerpb.PowerOnMachineRequest) (*providerpb.PowerOnMachineResponse, error) { + p.logger.Info("power on machine", zap.String("machine_id", request.Id)) + + machine, err := p.omniClient.GetMachine(ctx, request.Id) + if err != nil { + return nil, err + } + + if err = withClient(machine, func(client *goipmi.Client) error { + return client.Control(goipmi.ControlPowerUp) + }); err != nil { + return nil, err + } + + return &providerpb.PowerOnMachineResponse{}, nil +} + +func withClient(machine *resources.Machine, f func(client *goipmi.Client) error) error { + conn := &goipmi.Connection{ + Hostname: machine.TypedSpec().Value.IpmiIp, + Port: int(machine.TypedSpec().Value.IpmiPort), + Username: ipmiUsername, + Password: machine.TypedSpec().Value.IpmiPassword, + Interface: "lanplus", + } + + client, err := goipmi.NewClient(conn) + if err != nil { + return err + } + + defer client.Close() //nolint:errcheck + + return f(client) +} diff --git a/internal/tftp/tftp_server.go b/internal/tftp/tftp_server.go new file mode 100644 index 0000000..eaf43fa --- /dev/null +++ b/internal/tftp/tftp_server.go @@ -0,0 +1,124 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package tftp implements a TFTP server. +package tftp + +import ( + "context" + "io" + "os" + "path/filepath" + "time" + + "github.com/pin/tftp/v3" + "go.uber.org/zap" + "golang.org/x/sync/errgroup" + + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/constants" +) + +// Server represents the TFTP server serving iPXE binaries. +type Server struct { + logger *zap.Logger +} + +// NewServer creates a new TFTP server. +func NewServer(logger *zap.Logger) *Server { + return &Server{ + logger: logger, + } +} + +// Run runs the TFTP server. +func (s *Server) Run(ctx context.Context) error { + if err := os.MkdirAll(constants.TFTPPath, 0o777); err != nil { + return err + } + + readHandler := func(filename string, rf io.ReaderFrom) error { + return handleRead(filename, rf, s.logger) + } + + srv := tftp.NewServer(readHandler, nil) + + // A standard TFTP server implementation receives requests on port 69 and + // allocates a new high port (over 1024) dedicated to that request. In single + // port mode, the same port is used for transmit and receive. If the server + // is started on port 69, all communication will be done on port 69. + // This option is required since the Kubernetes service definition defines a + // single port. + srv.EnableSinglePort() + srv.SetTimeout(5 * time.Second) + + eg, ctx := errgroup.WithContext(ctx) + + eg.Go(func() error { + return srv.ListenAndServe(":69") + }) + + eg.Go(func() error { + <-ctx.Done() + + srv.Shutdown() + + return nil + }) + + return eg.Wait() +} + +// cleanPath makes a path safe for use with filepath.Join. This is done by not +// only cleaning the path, but also (if the path is relative) adding a leading +// '/' and cleaning it (then removing the leading '/'). This ensures that a +// path resulting from prepending another path will always resolve to lexically +// be a subdirectory of the prefixed path. This is all done lexically, so paths +// that include symlinks won't be safe as a result of using CleanPath. +func cleanPath(path string) string { + // Deal with empty strings nicely. + if path == "" { + return "" + } + + // Ensure that all paths are cleaned (especially problematic ones like + // "/../../../../../" which can cause lots of issues). + path = filepath.Clean(path) + + // If the path isn't absolute, we need to do more processing to fix paths + // such as "../../../..//some/path". We also shouldn't convert absolute + // paths to relative ones. + if !filepath.IsAbs(path) { + path = filepath.Clean(string(os.PathSeparator) + path) + // This can't fail, as (by definition) all paths are relative to root. + path, _ = filepath.Rel(string(os.PathSeparator), path) //nolint:errcheck + } + + // Clean the path again for good measure. + return filepath.Clean(path) +} + +// handleRead is called when a client starts file download from server. +func handleRead(filename string, rf io.ReaderFrom, logger *zap.Logger) error { + filename = filepath.Join(constants.TFTPPath, cleanPath(filename)) + + file, err := os.Open(filename) + if err != nil { + logger.Error("failed to open file", zap.String("filename", filename), zap.Error(err)) + + return err + } + + defer file.Close() //nolint:errcheck + + n, err := rf.ReadFrom(file) + if err != nil { + logger.Error("failed to read from file", zap.String("filename", filename), zap.Error(err)) + + return err + } + + logger.Info("file sent", zap.String("filename", filename), zap.Int64("bytes", n)) + + return nil +} diff --git a/internal/version/data/sha b/internal/version/data/sha new file mode 100644 index 0000000..66dc905 --- /dev/null +++ b/internal/version/data/sha @@ -0,0 +1 @@ +undefined \ No newline at end of file diff --git a/internal/version/data/tag b/internal/version/data/tag new file mode 100644 index 0000000..66dc905 --- /dev/null +++ b/internal/version/data/tag @@ -0,0 +1 @@ +undefined \ No newline at end of file diff --git a/internal/version/version.go b/internal/version/version.go new file mode 100644 index 0000000..b39b93f --- /dev/null +++ b/internal/version/version.go @@ -0,0 +1,41 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. +// +// Generated on 2024-10-14T09:32:55Z by kres 34e72ac. + +// Package version contains variables such as project name, tag and sha. It's a proper alternative to using +// -ldflags '-X ...'. +package version + +import ( + _ "embed" + "runtime/debug" + "strings" +) + +var ( + // Tag declares project git tag. + //go:embed data/tag + Tag string + // SHA declares project git SHA. + //go:embed data/sha + SHA string + // Name declares project name. + Name = func() string { + info, ok := debug.ReadBuildInfo() + if !ok { + panic("cannot read build info, something is very wrong") + } + + // Check if siderolabs project + if strings.HasPrefix(info.Path, "github.com/siderolabs/") { + return info.Path[strings.LastIndex(info.Path, "/")+1:] + } + + // We could return a proper full path here, but it could be seen as a privacy violation. + return "community-project" + }() +)