From 7440d7067f726b5c621ae4fac9ae0fd5373f2bb6 Mon Sep 17 00:00:00 2001 From: Utku Ozdemir Date: Mon, 14 Oct 2024 11:45:28 +0200 Subject: [PATCH] feat: implement bare-metal provider Add initial implementation of the bare-metal infra provider. Related to siderolabs/omni#660. Signed-off-by: Utku Ozdemir --- .codecov.yml | 18 + .conform.yaml | 48 + .dockerignore | 13 + .github/workflows/ci.yaml | 86 +- .gitignore | 6 + .golangci.yml | 149 +++ .kres.yaml | 139 +++ .license-header.go.txt | 3 + .markdownlint.json | 9 + .secrets.yaml | 82 ++ .sops.yaml | 13 + Dockerfile | 241 ++++ LICENSE | 373 ++++++ Makefile | 262 +++++ README.md | 105 ++ api/provider/provider.pb.go | 188 +++ api/provider/provider.proto | 19 + api/provider/provider_grpc.pb.go | 122 ++ api/provider/provider_vtproto.pb.go | 321 ++++++ api/specs/specs.pb.go | 485 ++++++++ api/specs/specs.proto | 49 + api/specs/specs_vtproto.pb.go | 1016 +++++++++++++++++ cmd/provider/main.go | 141 +++ cmd/qemu-up/main.go | 111 ++ go.mod | 159 +++ go.sum | 574 ++++++++++ hack/certs/key.private | 106 ++ hack/certs/key.public | 52 + hack/certs/localhost-key.pem | 28 + hack/certs/localhost.pem | 26 + hack/release.sh | 149 +++ hack/release.toml | 11 + hack/test/integration.sh | 148 +++ internal/provider/agent/controller.go | 182 +++ internal/provider/baremetal/baremetal.go | 15 + internal/provider/baremetal/machine_status.go | 46 + internal/provider/boot/boot.go | 84 ++ internal/provider/config/config.go | 86 ++ internal/provider/constants/constants.go | 14 + internal/provider/controllers/controllers.go | 6 + .../controllers/infra_machine_status.go | 396 +++++++ internal/provider/data/icon.svg | 1 + internal/provider/debug/debug.go | 6 + internal/provider/debug/disabled.go | 10 + internal/provider/debug/enabled.go | 10 + internal/provider/dhcp/dhcp.go | 6 + internal/provider/dhcp/proxy.go | 284 +++++ internal/provider/imagefactory/client.go | 135 +++ .../provider/imagefactory/imagefactory.go | 6 + internal/provider/ip/ip.go | 51 + internal/provider/ipxe/boot.go | 35 + internal/provider/ipxe/handler.go | 352 ++++++ internal/provider/ipxe/ipxe.go | 6 + internal/provider/ipxe/patch.go | 205 ++++ .../provider/machinestatus/machinestatus.go | 6 + internal/provider/machinestatus/poller.go | 159 +++ internal/provider/meta/meta.go | 9 + internal/provider/omni/omni.go | 91 ++ internal/provider/omni/tunnel/tunnel.go | 67 ++ internal/provider/options.go | 43 + internal/provider/power/api/api.go | 103 ++ internal/provider/power/api/manager.go | 101 ++ internal/provider/power/ipmi/ipmi.go | 72 ++ internal/provider/power/power.go | 46 + internal/provider/provider.go | 255 +++++ internal/provider/server/server.go | 150 +++ internal/provider/service/service.go | 53 + internal/provider/stateutil/stateutil.go | 41 + internal/provider/tftp/tftp_server.go | 126 ++ internal/qemu/machines.go | 297 +++++ internal/qemu/options.go | 36 + internal/qemu/qemu.go | 6 + internal/version/data/sha | 1 + internal/version/data/tag | 1 + internal/version/version.go | 41 + 75 files changed, 8849 insertions(+), 43 deletions(-) create mode 100644 .codecov.yml create mode 100644 .conform.yaml create mode 100644 .dockerignore 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 .secrets.yaml create mode 100644 .sops.yaml 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 cmd/qemu-up/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 hack/certs/key.private create mode 100644 hack/certs/key.public create mode 100644 hack/certs/localhost-key.pem create mode 100644 hack/certs/localhost.pem create mode 100755 hack/release.sh create mode 100644 hack/release.toml create mode 100755 hack/test/integration.sh create mode 100644 internal/provider/agent/controller.go create mode 100644 internal/provider/baremetal/baremetal.go create mode 100644 internal/provider/baremetal/machine_status.go create mode 100644 internal/provider/boot/boot.go create mode 100644 internal/provider/config/config.go create mode 100644 internal/provider/constants/constants.go create mode 100644 internal/provider/controllers/controllers.go create mode 100644 internal/provider/controllers/infra_machine_status.go create mode 100644 internal/provider/data/icon.svg create mode 100644 internal/provider/debug/debug.go create mode 100644 internal/provider/debug/disabled.go create mode 100644 internal/provider/debug/enabled.go create mode 100644 internal/provider/dhcp/dhcp.go create mode 100644 internal/provider/dhcp/proxy.go create mode 100644 internal/provider/imagefactory/client.go create mode 100644 internal/provider/imagefactory/imagefactory.go create mode 100644 internal/provider/ip/ip.go create mode 100644 internal/provider/ipxe/boot.go create mode 100644 internal/provider/ipxe/handler.go create mode 100644 internal/provider/ipxe/ipxe.go create mode 100644 internal/provider/ipxe/patch.go create mode 100644 internal/provider/machinestatus/machinestatus.go create mode 100644 internal/provider/machinestatus/poller.go create mode 100644 internal/provider/meta/meta.go create mode 100644 internal/provider/omni/omni.go create mode 100644 internal/provider/omni/tunnel/tunnel.go create mode 100644 internal/provider/options.go create mode 100644 internal/provider/power/api/api.go create mode 100644 internal/provider/power/api/manager.go create mode 100644 internal/provider/power/ipmi/ipmi.go create mode 100644 internal/provider/power/power.go create mode 100644 internal/provider/provider.go create mode 100644 internal/provider/server/server.go create mode 100644 internal/provider/service/service.go create mode 100644 internal/provider/stateutil/stateutil.go create mode 100644 internal/provider/tftp/tftp_server.go create mode 100644 internal/qemu/machines.go create mode 100644 internal/qemu/options.go create mode 100644 internal/qemu/qemu.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 index 10f67bc..87159ab 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -76,55 +76,55 @@ jobs: - name: base run: | make base - - name: unit-tests - run: | - make unit-tests - - name: unit-tests-race - run: | - make unit-tests-race +# - name: unit-tests +# run: | +# make unit-tests +# - name: unit-tests-race +# run: | +# make unit-tests-race - 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: 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: qemu-up run: | make qemu-up - - 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-qemu-up - run: | - make image-qemu-up - - name: push-qemu-up - if: github.event_name != 'pull_request' - env: - PLATFORM: linux/amd64,linux/arm64 - PUSH: "true" - run: | - make image-qemu-up +# - 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-qemu-up +# run: | +# make image-qemu-up +# - name: push-qemu-up +# if: github.event_name != 'pull_request' +# env: +# PLATFORM: linux/amd64,linux/arm64 +# PUSH: "true" +# run: | +# make image-qemu-up - name: run-integration-test run: | sudo -E make run-integration-test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..919d453 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. +# +# Generated on 2024-11-28T09:31:42Z by kres 232fe63. + +_out +hack/compose/docker-compose.override.yml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..8712a59 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,149 @@ +# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. +# +# Generated on 2024-11-18T15:38:35Z by kres 91b35db. + +# 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 + - 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..e3ac266 --- /dev/null +++ b/.kres.yaml @@ -0,0 +1,139 @@ +--- +kind: common.Build +spec: + ignoredPaths: + - hack/compose/docker-compose.override.yml +--- +kind: common.Image +name: image-metal-agent +spec: + pushLatest: false + extraEnvironment: + PLATFORM: linux/amd64,linux/arm64 +--- +kind: common.Image +name: image-qemu-up +spec: + baseImage: ghcr.io/siderolabs/talosctl:v1.9.0-alpha.3 + pushLatest: false + extraEnvironment: + PLATFORM: linux/amd64,linux/arm64 +--- +kind: custom.Step +name: ipxe +spec: + docker: + enabled: true + stages: + - name: ipxe-linux-amd64 + from: ghcr.io/siderolabs/ipxe:v1.8.0-16-g71d23b4 + platform: linux/amd64 + - name: ipxe-linux-arm64 + from: ghcr.io/siderolabs/ipxe:v1.8.0-16-g71d23b4 + platform: linux/arm64 +--- +kind: auto.CustomSteps +spec: + steps: + - name: ipxe + toplevel: true + - name: run-integration-test + toplevel: true +--- +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: ipxe-linux-amd64 + source: /usr/libexec/ + destination: /var/lib/ipxe/amd64 + - stage: ipxe-linux-arm64 + source: /usr/libexec/ + destination: /var/lib/ipxe/arm64 + - + stage: ghcr.io/siderolabs/talos-metal-agent-boot-assets:v1.9.0-alpha.3-agent-v0.1.0-alpha.2 # to be used with --use-local-boot-assets for local development + # stage: 127.0.0.1:5005/siderolabs/talos-metal-agent-boot-assets:v0.0.1-local # for local development, to be replaced with the line above and rekres-ed + source: / + destination: /assets +--- +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: + enabled: false +--- +kind: custom.Step +name: run-integration-test +spec: + sudoInCI: true + makefile: + enabled: true + depends: + - provider + - qemu-up + script: + - >- + @hack/test/integration.sh + ghaction: + enabled: true + sops: true + artifacts: + enabled: true + additional: + - name: talos-logs + always: true + continueOnError: true + paths: + - "~/.talos/clusters/**/*.log" + - "!~/.talos/clusters/**/swtpm.log" +--- +kind: common.SOPS +spec: + enabled: true + config: |- + creation_rules: + - age: age1xrpa9ujxxcj2u2gzfrzv8mxak4rts94a6y60ypurv6rs5cpr4e4sg95f0k + # order: Andrey, Noel, Artem, Utku, Dmitriy + pgp: >- + 15D5721F5F5BAF121495363EFE042E3D4085A811, + CC51116A94490FA6FB3C18EB2401FCAE863A06CA, + 4919F560F0D35F80CF382D76E084A2DF1143C14D, + 11177A43C6E3752E682AC690DBD13117B0A14E93, + AA5213AF261C1977AF38B03A94B473337258BFD5 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/.secrets.yaml b/.secrets.yaml new file mode 100644 index 0000000..883034a --- /dev/null +++ b/.secrets.yaml @@ -0,0 +1,82 @@ +secrets: + AUTH0_TEST_USERNAME: ENC[AES256_GCM,data:lPddHbDVfWxaEW7ujLDnWdhIBMFj2hcp,iv:oG3Ebn8ym7g/Z7L3A3BTHRHIk+zzblZKvzMKYMPSfWI=,tag:wV7xJWbnLrj/UWj0fGGQCw==,type:str] + AUTH0_TEST_PASSWORD: ENC[AES256_GCM,data:3tgQjqv5ktdnnGUQw5Lpuw==,iv:F8zYxqk5P0tV1Pvt6QBlho8H0wuX+K91pgwLzF+4kC8=,tag:HJ4s14d/u2KyP780wFDk/w==,type:str] + AUTH0_CLIENT_ID: ENC[AES256_GCM,data:HevA8uFKCOPF8W/FRjSo/pyUFN66eXwvAxaqT5LdnT0=,iv:qpWNjsRSZ28lWQJGfMoGQvLY8KRKWv1dhR07vCgIvIU=,tag:x5BS26iacdBMv2ZkdCdr3A==,type:str] + AUTH0_DOMAIN: ENC[AES256_GCM,data:2vv9ay+hC1kN46MG8E0v1Z3G7Dm0hMmLx1/AWg==,iv:9thZflFQ1yhf0jH3u6Om7RV7Y/qYzrTf82hoYrDvyG0=,tag:BUNuHJobt/NoR5FFQBIbIQ==,type:str] +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: + - recipient: age1xrpa9ujxxcj2u2gzfrzv8mxak4rts94a6y60ypurv6rs5cpr4e4sg95f0k + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRRC9PWFJkK2FJa3N0UHlv + WkJGQVZCVUl4NEhqRDdiUm03TC9NQTVmWm5vCmNFZGdCUE1OMzZiNnpoSnR0UVYx + UVF2Rzg4VEdSdGNZdUNXd2V5UGdrOTAKLS0tIEQvNjVjUlRPZFBuK2M4dS9KYTFR + a3QwZCtyZ0NBQXo4cmFRazVwWlZEWFkKIHUHaImxN+SgMVSd6pgMQyiAy+mTQAaQ + mGIKj1vrDXs1FLAl5lkV7IxFkf51qqTk6rOxjv1zCzFYLATAr3t4eg== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2024-05-06T09:52:57Z" + mac: ENC[AES256_GCM,data:4qmhG/liKJdnEBxvvnxnpb9xJpS8GGjCAHGUVM4dGtYY5+TkfgnSQyvVdg88Ag16nMDTBEeRJO6VfOYD/Wx/PfIYnajhxRm3ZYuPPSJ5t0LGqRryUtR9vJTtHuTew5gjX8FCTvjiGJzqcfTiq11HhN3Xyu7VNwwan50QUvz5oKY=,iv:Rc0/1kH74ahBkNygwFrOZymWMnPj3VCQZ7wBi1d7Rzc=,tag:Cgdjhlc24S2gklSKYe5mPw==,type:str] + pgp: + - created_at: "2024-11-27T13:27:48Z" + enc: |- + -----BEGIN PGP MESSAGE----- + + hF4D/dYBJRlWfQISAQdAWb3gbi9Rm9Ery5O4vjcms/Inx26KJ8SODWRZ1t/k7nkw + I+u059duWr0e7O2ykSzDnGQA6Aj+HdhPoPcdnNlBWpy3raPwKzJ+X8kVIYp3qiba + 0lwB4HMtkNBpz91ErblPNWiVVVe3+G8LLJpDTpaVuPNsj1d3OgnKnWnVFK0b8FAL + kQSmurQgKKbWZ7R6uhfWLINBR4ICZg51FViURfpWoUtlGvrL+nnbvmy9hQVGMQ== + =7teE + -----END PGP MESSAGE----- + fp: 15D5721F5F5BAF121495363EFE042E3D4085A811 + - created_at: "2024-11-27T13:27:48Z" + enc: |- + -----BEGIN PGP MESSAGE----- + + hF4D+EORkHurkvgSAQdAkbVUYD2I5MOoPjRjTRI6EfMlA8oEGsMu+ovxmEyMLwkw + egHUl/oDTgovO+12mC/iRAhaKzV5fvCeZUTDbPB7BT+a2HsGyQEE2O4JYrSY/EMJ + 0lwBe8TdlHF9HUqwzHbRTo8UDbap0gpJ/KngXJ51m90Az6u4+lmpPgcgWSyJAnBF + u/rjpjqobTmwR8Ea7NZpF3ZcoltlJLyd6w0O7JD5hd8nVWEhbN2KDHwKAnbneQ== + =aBI7 + -----END PGP MESSAGE----- + fp: CC51116A94490FA6FB3C18EB2401FCAE863A06CA + - created_at: "2024-11-27T13:27:48Z" + enc: |- + -----BEGIN PGP MESSAGE----- + + hF4DCsA/BhMt3V4SAQdAasIYIdDD+bb/JHfxT0yCMbxOu0JFB95rVSMTzLLw+BAw + K8XVL7U+2EbmjO2OkTEbI80SEu//L/c/ZjmvTU8dt18WWMWb/FDWYGrw40KBMbts + 0lwBUk4UzZ38uDmnNPka1k6vBPWxvfIOHylbZYFoC4oLXEkghj70cPji6ZBUxpe1 + d6Avs0cXx5PLbM2lL/Rh/+9dAhSkl+Uzc5kEXfYPW0IBNlHaH1By+wsoVTgsGQ== + =+zAf + -----END PGP MESSAGE----- + fp: 4919F560F0D35F80CF382D76E084A2DF1143C14D + - created_at: "2024-11-27T13:27:48Z" + enc: |- + -----BEGIN PGP MESSAGE----- + + hF4DRbry8yWl6IgSAQdAJoHdZndKL5N3KuO+gNofIpZYKhihf0L5MP+tG5LPKD0w + Hhm5QOMjYNT7LjOTKtRb59ymKSP0oLiQMNvphw4q0IYsFz+l4UkanT6IO6K5tdvQ + 1GYBCQIQrIdQeujzcbTI2z9Iwh8RnBxdEhhBYSZMyrzBS7B2V5p0qpjaFojbI3oa + A4uaNEpxnk59mfCuZeKHNURVHn0VT/jZdzalC7aQMk1h/7w6cjZLjJ/Skn5t1Obp + mQKG7tBZzLo= + =PH0s + -----END PGP MESSAGE----- + fp: 11177A43C6E3752E682AC690DBD13117B0A14E93 + - created_at: "2024-11-27T13:27:48Z" + enc: |- + -----BEGIN PGP MESSAGE----- + + hF4DzfZC0UNQ1VgSAQdAZ0yc24RcLD0R7Bs4UPQTAfbp5Y/DtanIv7kOSjVtlAEw + +F1qLNuwR0r1K9HLndgu0G5vi3L3ra8drb6YMKR7kXyN9RLXjDBt8gD3s386VGMa + 1GYBCQIQx6GailOK4BBn2H8HQqDTEgrsLj0ZhG6jOCwKpdPD3VoC4OWDJ0yzt72Z + fwP3VgEH1IB+QF+XBJgdJsh54d79UDutBLxIYJAInQ3foxD3DOQ96D0onysA/qqh + ZV7CB+9hCYc= + =36ga + -----END PGP MESSAGE----- + fp: AA5213AF261C1977AF38B03A94B473337258BFD5 + unencrypted_suffix: _unencrypted + version: 3.8.1 diff --git a/.sops.yaml b/.sops.yaml new file mode 100644 index 0000000..a535d98 --- /dev/null +++ b/.sops.yaml @@ -0,0 +1,13 @@ +# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. +# +# Generated on 2024-11-28T09:26:49Z by kres 232fe63. + +creation_rules: + - age: age1xrpa9ujxxcj2u2gzfrzv8mxak4rts94a6y60ypurv6rs5cpr4e4sg95f0k + # order: Andrey, Noel, Artem, Utku, Dmitriy + pgp: >- + 15D5721F5F5BAF121495363EFE042E3D4085A811, + CC51116A94490FA6FB3C18EB2401FCAE863A06CA, + 4919F560F0D35F80CF382D76E084A2DF1143C14D, + 11177A43C6E3752E682AC690DBD13117B0A14E93, + AA5213AF261C1977AF38B03A94B473337258BFD5 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..baafae0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,241 @@ +# syntax = docker/dockerfile-upstream:1.11.1-labs + +# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. +# +# Generated on 2024-11-27T11:13:27Z by kres 232fe63. + +ARG TOOLCHAIN + +FROM ghcr.io/siderolabs/talosctl:v1.9.0-alpha.3 AS base-image-qemu-up + +FROM ghcr.io/siderolabs/ca-certificates:v1.8.0 AS image-ca-certificates + +FROM ghcr.io/siderolabs/fhs:v1.8.0 AS image-fhs + +FROM --platform=linux/amd64 ghcr.io/siderolabs/ipxe:v1.8.0-16-g71d23b4 AS ipxe-linux-amd64 + +FROM --platform=linux/arm64 ghcr.io/siderolabs/ipxe:v1.8.0-16-g71d23b4 AS ipxe-linux-arm64 + +# runs markdownlint +FROM docker.io/oven/bun:1.1.36-alpine AS lint-markdown +WORKDIR /src +RUN bun i markdownlint-cli@0.43.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 + +# builds qemu-up-linux-amd64 +FROM base AS qemu-up-linux-amd64-build +COPY --from=generate / / +COPY --from=embed-generate / / +WORKDIR /src/cmd/qemu-up +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=qemu-up -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG}" -o /qemu-up-linux-amd64 + +# builds qemu-up-linux-arm64 +FROM base AS qemu-up-linux-arm64-build +COPY --from=generate / / +COPY --from=embed-generate / / +WORKDIR /src/cmd/qemu-up +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=qemu-up -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG}" -o /qemu-up-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 scratch AS qemu-up-linux-amd64 +COPY --from=qemu-up-linux-amd64-build /qemu-up-linux-amd64 /qemu-up-linux-amd64 + +FROM scratch AS qemu-up-linux-arm64 +COPY --from=qemu-up-linux-arm64-build /qemu-up-linux-arm64 /qemu-up-linux-arm64 + +FROM provider-linux-${TARGETARCH} AS provider + +FROM scratch AS provider-all +COPY --from=provider-linux-amd64 / / +COPY --from=provider-linux-arm64 / / + +FROM qemu-up-linux-${TARGETARCH} AS qemu-up + +FROM scratch AS qemu-up-all +COPY --from=qemu-up-linux-amd64 / / +COPY --from=qemu-up-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=ipxe-linux-amd64 /usr/libexec/ /var/lib/ipxe/amd64 +COPY --from=ipxe-linux-arm64 /usr/libexec/ /var/lib/ipxe/arm64 +COPY --from=ghcr.io/siderolabs/talos-metal-agent-boot-assets:v1.9.0-alpha.3-agent-v0.1.0-alpha.2 / /assets +LABEL org.opencontainers.image.source=https://github.com/siderolabs/omni-infra-provider-bare-metal +ENTRYPOINT ["/provider"] + +FROM base-image-qemu-up AS image-qemu-up +ARG TARGETARCH +COPY --from=qemu-up qemu-up-linux-${TARGETARCH} /qemu-up +COPY --from=image-fhs / / +COPY --from=image-ca-certificates / / +LABEL org.opencontainers.image.source=https://github.com/siderolabs/omni-infra-provider-bare-metal +ENTRYPOINT ["/qemu-up"] + 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..822a495 --- /dev/null +++ b/Makefile @@ -0,0 +1,262 @@ +# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. +# +# Generated on 2024-11-28T09:31:42Z by kres 232fe63. + +# 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.2 +GRPC_GO_VERSION ?= 1.5.1 +GRPC_GATEWAY_VERSION ?= 2.24.0 +VTPROTOBUF_VERSION ?= 0.6.0 +GOIMPORTS_VERSION ?= 0.27.0 +DEEPCOPY_VERSION ?= v0.5.6 +GOLANGCILINT_VERSION ?= v1.62.0 +GOFUMPT_VERSION ?= v0.7.0 +GO_VERSION ?= 1.23.3 +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 ?= +BUILDKIT_MULTI_PLATFORM ?= 1 +COMMON_ARGS = --file=Dockerfile +COMMON_ARGS += --provenance=false +COMMON_ARGS += --progress=$(PROGRESS) +COMMON_ARGS += --platform=$(PLATFORM) +COMMON_ARGS += --push=$(PUSH) +COMMON_ARGS += --build-arg=BUILDKIT_MULTI_PLATFORM=$(BUILDKIT_MULTI_PLATFORM) +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 qemu-up image-qemu-up ipxe run-integration-test 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)" + @PLATFORM=$(PLATFORM) DEST=$(DEST) bash -c '\ + for platform in $$(tr "," "\n" <<< "$$PLATFORM"); do \ + echo $$platform; \ + directory="$${platform//\//_}"; \ + if [[ -d "$$DEST/$$directory" ]]; then \ + mv "$$DEST/$$directory/"* $$DEST; \ + rmdir "$$DEST/$$directory/"; \ + fi; \ + done' + +generate: ## Generate .proto definitions. + @$(MAKE) local-$@ DEST=./ BUILDKIT_MULTI_PLATFORM=0 + +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: $(ARTIFACTS)/qemu-up-linux-amd64 +$(ARTIFACTS)/qemu-up-linux-amd64: + @$(MAKE) local-qemu-up-linux-amd64 DEST=$(ARTIFACTS) + +.PHONY: qemu-up-linux-amd64 +qemu-up-linux-amd64: $(ARTIFACTS)/qemu-up-linux-amd64 ## Builds executable for qemu-up-linux-amd64. + +.PHONY: $(ARTIFACTS)/qemu-up-linux-arm64 +$(ARTIFACTS)/qemu-up-linux-arm64: + @$(MAKE) local-qemu-up-linux-arm64 DEST=$(ARTIFACTS) + +.PHONY: qemu-up-linux-arm64 +qemu-up-linux-arm64: $(ARTIFACTS)/qemu-up-linux-arm64 ## Builds executable for qemu-up-linux-arm64. + +.PHONY: qemu-up +qemu-up: qemu-up-linux-amd64 qemu-up-linux-arm64 ## Builds executables for qemu-up. + +.PHONY: image-qemu-up +image-qemu-up: ## Builds image for qemu-up. + @$(MAKE) target-$@ TARGET_ARGS="--tag=$(REGISTRY)/$(USERNAME)/qemu-up:$(IMAGE_TAG)" + +run-integration-test: provider qemu-up + @hack/test/integration.sh + +.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..5d0bd23 100644 --- a/README.md +++ b/README.md @@ -1 +1,106 @@ # omni-infra-provider-bare-metal + +This repo contains the code for the Omni Bare Metal Infra Provider. + +## Requirements + +To run the provider, you need: + +- A running Omni instance +- An Omni infra provider service account matching the ID you'll use with this provider (`bare-metal` by default). + To create it, run: + + ```bash + omnictl serviceaccount create --use-user-role=false --role=InfraProvider infra-provider:bare-metal + ``` + + Replace `bare-metal` with your desired provider ID. +- A DHCP server: This provider runs a DHCP proxy to provide DHCP responses for iPXE boot, so a DHCP server must be running in the same network as the provider. +- Access to an [Image Factory](https://www.talos.dev/v1.8/learn-more/image-factory/). + +## Development + +For local development using Talos running on QEMU, follow these steps: + +1. Set up a `buildx` builder instance with host network access, if you don't have one already: + + ```bash + docker buildx create --driver docker-container --driver-opt network=host --name local1 --buildkitd-flags '--allow-insecure-entitlement security.insecure' --use + ``` + +2. Start a local image registry if you don't have one running: + + ```bash + docker run -d -p 5005:5000 --restart always --name local registry:2 + ``` + +3. Start Talos nodes on QEMU, set to PXE boot: + + ```bash + talosctl cluster create \ + --provisioner=qemu \ + --cidr=172.20.0.0/24 \ + --controlplanes=3 \ + --workers=0 \ + --disable-dhcp-hostname=true \ + --with-bootloader=true \ + --with-apply-config=false \ + --skip-injecting-config=true \ + --wait=false \ + --with-init-node=true \ + --default-boot-order=nc \ + --pxe-booted \ + --memory=8192 \ + --cpus=6 \ + --with-uefi=false \ + --with-uuid-hostnames + ``` + +4. (Optional) If you have made local changes to the [Talos Metal agent](https://github.com/siderolabs/talos-metal-agent), follow these steps to use your local version: + 1. Build and push Talos Metal Agent boot assets image following [these instructions](https://github.com/siderolabs/talos-metal-agent/blob/main/README.md). + 2. Replace the `ghcr.io/siderolabs/talos-metal-agent-boot-assets` image reference in [.kres.yaml](.kres.yaml) with your built image, + e.g., `127.0.0.1:5005/siderolabs/talos-metal-agent-boot-assets:v1.9.0-alpha.2-agent-v0.1.0-alpha.0-1-gbf1282b-dirty`. + 3. Re-kres the project to propagate this change into `Dockerfile`: + + ```bash + make rekres + ``` + +5. Build a local provider image: + + ```bash + make image-provider PLATFORM=linux/amd64 REGISTRY=127.0.0.1:5005 PUSH=true TAG=local-dev + docker pull 127.0.0.1:5005/siderolabs/provider:local-dev + ``` + +6. Start the provider with your Omni API address and service account credentials: + + ```bash + export OMNI_ENDPOINT= + export OMNI_SERVICE_ACCOUNT_KEY= + + docker run --name=omni-bare-metal-provider --network host --rm -it \ + -v "$HOME/.talos/clusters/talos-default:/api-power-mgmt-state:ro" \ + -e OMNI_ENDPOINT -e OMNI_SERVICE_ACCOUNT_KEY \ + 127.0.0.1:5005/siderolabs/provider:local-dev \ + --insecure-skip-tls-verify \ + --api-advertise-address= \ + --use-local-boot-assets \ + --agent-test-mode \ + --api-power-mgmt-state-dir=/api-power-mgmt-state \ + --dhcp-proxy-iface-or-ip=172.20.0.1 \ + --debug + ``` + + Important flags: + - `--use-local-boot-assets`: Makes the provider serve the boot assets image embedded in the provider image. + This is useful for testing local Talos Metal Agent boot assets. + Omit this flag to use the upstream agent version, which will forward agent mode PXE boot requests to the image factory. + - `--agent-test-mode`: Boots the agent in test mode when booting a Talos node in agent mode, enabling API-based power management instead of IPMI/RedFish. + This is necessary for QEMU development, + as it uses the power management API run by the `talosctl cluster create` command. + - The volume mount `-v "$HOME/.talos/clusters/talos-default:/api-power-mgmt-state:ro"` + mounts the directory containing API-based power management state information generated by `talosctl cluster create`. + - `--api-power-mgmt-state-dir`: Specifies where to read the API power management address of the nodes. + - `--dhcp-proxy-iface-or-ip`: Specifies the IP address or interface name for running the DHCP proxy + (e.g., the IP address of the QEMU bridge interface). diff --git a/api/provider/provider.pb.go b/api/provider/provider.pb.go new file mode 100644 index 0000000..0feba5f --- /dev/null +++ b/api/provider/provider.pb.go @@ -0,0 +1,188 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.2 +// 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 RebootMachineRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *RebootMachineRequest) Reset() { + *x = RebootMachineRequest{} + mi := &file_provider_provider_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RebootMachineRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RebootMachineRequest) ProtoMessage() {} + +func (x *RebootMachineRequest) 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 RebootMachineRequest.ProtoReflect.Descriptor instead. +func (*RebootMachineRequest) Descriptor() ([]byte, []int) { + return file_provider_provider_proto_rawDescGZIP(), []int{0} +} + +func (x *RebootMachineRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type RebootMachineResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *RebootMachineResponse) Reset() { + *x = RebootMachineResponse{} + mi := &file_provider_provider_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RebootMachineResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RebootMachineResponse) ProtoMessage() {} + +func (x *RebootMachineResponse) 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 RebootMachineResponse.ProtoReflect.Descriptor instead. +func (*RebootMachineResponse) Descriptor() ([]byte, []int) { + return file_provider_provider_proto_rawDescGZIP(), []int{1} +} + +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, 0x11, 0x62, 0x61, 0x72, 0x65, 0x6d, + 0x65, 0x74, 0x61, 0x6c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 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, 0x26, 0x0a, 0x14, 0x52, 0x65, + 0x62, 0x6f, 0x6f, 0x74, 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, 0x17, 0x0a, 0x15, 0x52, 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x4d, 0x61, 0x63, 0x68, + 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x75, 0x0a, 0x0f, 0x50, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x62, + 0x0a, 0x0d, 0x52, 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, + 0x27, 0x2e, 0x62, 0x61, 0x72, 0x65, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x62, 0x61, 0x72, 0x65, 0x6d, + 0x65, 0x74, 0x61, 0x6c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x62, + 0x6f, 0x6f, 0x74, 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, 2) +var file_provider_provider_proto_goTypes = []any{ + (*RebootMachineRequest)(nil), // 0: baremetalprovider.RebootMachineRequest + (*RebootMachineResponse)(nil), // 1: baremetalprovider.RebootMachineResponse +} +var file_provider_provider_proto_depIdxs = []int32{ + 0, // 0: baremetalprovider.ProviderService.RebootMachine:input_type -> baremetalprovider.RebootMachineRequest + 1, // 1: baremetalprovider.ProviderService.RebootMachine:output_type -> baremetalprovider.RebootMachineResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] 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: 2, + 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..6dc3277 --- /dev/null +++ b/api/provider/provider.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +package baremetalprovider; + +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 RebootMachineRequest { + string id = 1; +} + +message RebootMachineResponse {} + +service ProviderService { + rpc RebootMachine(RebootMachineRequest) returns (RebootMachineResponse); +} diff --git a/api/provider/provider_grpc.pb.go b/api/provider/provider_grpc.pb.go new file mode 100644 index 0000000..d827643 --- /dev/null +++ b/api/provider/provider_grpc.pb.go @@ -0,0 +1,122 @@ +// 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_RebootMachine_FullMethodName = "/baremetalprovider.ProviderService/RebootMachine" +) + +// 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 { + RebootMachine(ctx context.Context, in *RebootMachineRequest, opts ...grpc.CallOption) (*RebootMachineResponse, error) +} + +type providerServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewProviderServiceClient(cc grpc.ClientConnInterface) ProviderServiceClient { + return &providerServiceClient{cc} +} + +func (c *providerServiceClient) RebootMachine(ctx context.Context, in *RebootMachineRequest, opts ...grpc.CallOption) (*RebootMachineResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RebootMachineResponse) + err := c.cc.Invoke(ctx, ProviderService_RebootMachine_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 { + RebootMachine(context.Context, *RebootMachineRequest) (*RebootMachineResponse, 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) RebootMachine(context.Context, *RebootMachineRequest) (*RebootMachineResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RebootMachine 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_RebootMachine_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RebootMachineRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServiceServer).RebootMachine(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ProviderService_RebootMachine_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServiceServer).RebootMachine(ctx, req.(*RebootMachineRequest)) + } + 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: "baremetalprovider.ProviderService", + HandlerType: (*ProviderServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "RebootMachine", + Handler: _ProviderService_RebootMachine_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..c057c50 --- /dev/null +++ b/api/provider/provider_vtproto.pb.go @@ -0,0 +1,321 @@ +// 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 *RebootMachineRequest) CloneVT() *RebootMachineRequest { + if m == nil { + return (*RebootMachineRequest)(nil) + } + r := new(RebootMachineRequest) + 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 *RebootMachineRequest) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (m *RebootMachineResponse) CloneVT() *RebootMachineResponse { + if m == nil { + return (*RebootMachineResponse)(nil) + } + r := new(RebootMachineResponse) + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *RebootMachineResponse) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (this *RebootMachineRequest) EqualVT(that *RebootMachineRequest) 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 *RebootMachineRequest) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*RebootMachineRequest) + if !ok { + return false + } + return this.EqualVT(that) +} +func (this *RebootMachineResponse) EqualVT(that *RebootMachineResponse) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *RebootMachineResponse) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*RebootMachineResponse) + if !ok { + return false + } + return this.EqualVT(that) +} +func (m *RebootMachineRequest) 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 *RebootMachineRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *RebootMachineRequest) 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 *RebootMachineResponse) 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 *RebootMachineResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *RebootMachineResponse) 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 *RebootMachineRequest) 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 *RebootMachineResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + n += len(m.unknownFields) + return n +} + +func (m *RebootMachineRequest) 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: RebootMachineRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RebootMachineRequest: 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 *RebootMachineResponse) 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: RebootMachineResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RebootMachineResponse: 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..5de7e9e --- /dev/null +++ b/api/specs/specs.pb.go @@ -0,0 +1,485 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.2 +// 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" +) + +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 BootMode int32 + +const ( + BootMode_BOOT_MODE_UNKNOWN BootMode = 0 + BootMode_BOOT_MODE_AGENT_PXE BootMode = 1 + BootMode_BOOT_MODE_TALOS_PXE BootMode = 2 + BootMode_BOOT_MODE_TALOS_DISK BootMode = 3 +) + +// Enum value maps for BootMode. +var ( + BootMode_name = map[int32]string{ + 0: "BOOT_MODE_UNKNOWN", + 1: "BOOT_MODE_AGENT_PXE", + 2: "BOOT_MODE_TALOS_PXE", + 3: "BOOT_MODE_TALOS_DISK", + } + BootMode_value = map[string]int32{ + "BOOT_MODE_UNKNOWN": 0, + "BOOT_MODE_AGENT_PXE": 1, + "BOOT_MODE_TALOS_PXE": 2, + "BOOT_MODE_TALOS_DISK": 3, + } +) + +func (x BootMode) Enum() *BootMode { + p := new(BootMode) + *p = x + return p +} + +func (x BootMode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (BootMode) Descriptor() protoreflect.EnumDescriptor { + return file_specs_specs_proto_enumTypes[0].Descriptor() +} + +func (BootMode) Type() protoreflect.EnumType { + return &file_specs_specs_proto_enumTypes[0] +} + +func (x BootMode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use BootMode.Descriptor instead. +func (BootMode) EnumDescriptor() ([]byte, []int) { + return file_specs_specs_proto_rawDescGZIP(), []int{0} +} + +type PowerState int32 + +const ( + PowerState_POWER_STATE_UNKNOWN PowerState = 0 + PowerState_POWER_STATE_OFF PowerState = 1 + PowerState_POWER_STATE_ON PowerState = 2 +) + +// Enum value maps for PowerState. +var ( + PowerState_name = map[int32]string{ + 0: "POWER_STATE_UNKNOWN", + 1: "POWER_STATE_OFF", + 2: "POWER_STATE_ON", + } + PowerState_value = map[string]int32{ + "POWER_STATE_UNKNOWN": 0, + "POWER_STATE_OFF": 1, + "POWER_STATE_ON": 2, + } +) + +func (x PowerState) Enum() *PowerState { + p := new(PowerState) + *p = x + return p +} + +func (x PowerState) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (PowerState) Descriptor() protoreflect.EnumDescriptor { + return file_specs_specs_proto_enumTypes[1].Descriptor() +} + +func (PowerState) Type() protoreflect.EnumType { + return &file_specs_specs_proto_enumTypes[1] +} + +func (x PowerState) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use PowerState.Descriptor instead. +func (PowerState) EnumDescriptor() ([]byte, []int) { + return file_specs_specs_proto_rawDescGZIP(), []int{1} +} + +type PowerManagement struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Ipmi *PowerManagement_IPMI `protobuf:"bytes,1,opt,name=ipmi,proto3" json:"ipmi,omitempty"` + Api *PowerManagement_API `protobuf:"bytes,2,opt,name=api,proto3" json:"api,omitempty"` +} + +func (x *PowerManagement) Reset() { + *x = PowerManagement{} + mi := &file_specs_specs_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PowerManagement) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PowerManagement) ProtoMessage() {} + +func (x *PowerManagement) 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 PowerManagement.ProtoReflect.Descriptor instead. +func (*PowerManagement) Descriptor() ([]byte, []int) { + return file_specs_specs_proto_rawDescGZIP(), []int{0} +} + +func (x *PowerManagement) GetIpmi() *PowerManagement_IPMI { + if x != nil { + return x.Ipmi + } + return nil +} + +func (x *PowerManagement) GetApi() *PowerManagement_API { + if x != nil { + return x.Api + } + return nil +} + +type MachineStatusSpec struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PowerManagement *PowerManagement `protobuf:"bytes,1,opt,name=power_management,json=powerManagement,proto3" json:"power_management,omitempty"` + PowerState PowerState `protobuf:"varint,2,opt,name=power_state,json=powerState,proto3,enum=baremetalproviderspecs.PowerState" json:"power_state,omitempty"` + // LastBootMode is the last observed boot mode of the machine. It is updated by the PXE server each time it boots a server, + // and is also updated by the status of the agent connectivity. + BootMode BootMode `protobuf:"varint,3,opt,name=boot_mode,json=bootMode,proto3,enum=baremetalproviderspecs.BootMode" json:"boot_mode,omitempty"` + // LastWipeId is the ID of the last wipe operation that was performed on the machine. + // + // It is used to track if the machine needs to be wiped for an allocation. + LastWipeId string `protobuf:"bytes,4,opt,name=last_wipe_id,json=lastWipeId,proto3" json:"last_wipe_id,omitempty"` +} + +func (x *MachineStatusSpec) Reset() { + *x = MachineStatusSpec{} + mi := &file_specs_specs_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MachineStatusSpec) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MachineStatusSpec) ProtoMessage() {} + +func (x *MachineStatusSpec) ProtoReflect() protoreflect.Message { + mi := &file_specs_specs_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 MachineStatusSpec.ProtoReflect.Descriptor instead. +func (*MachineStatusSpec) Descriptor() ([]byte, []int) { + return file_specs_specs_proto_rawDescGZIP(), []int{1} +} + +func (x *MachineStatusSpec) GetPowerManagement() *PowerManagement { + if x != nil { + return x.PowerManagement + } + return nil +} + +func (x *MachineStatusSpec) GetPowerState() PowerState { + if x != nil { + return x.PowerState + } + return PowerState_POWER_STATE_UNKNOWN +} + +func (x *MachineStatusSpec) GetBootMode() BootMode { + if x != nil { + return x.BootMode + } + return BootMode_BOOT_MODE_UNKNOWN +} + +func (x *MachineStatusSpec) GetLastWipeId() string { + if x != nil { + return x.LastWipeId + } + return "" +} + +type PowerManagement_IPMI struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` + Username string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"` + Password string `protobuf:"bytes,4,opt,name=password,proto3" json:"password,omitempty"` +} + +func (x *PowerManagement_IPMI) Reset() { + *x = PowerManagement_IPMI{} + mi := &file_specs_specs_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PowerManagement_IPMI) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PowerManagement_IPMI) ProtoMessage() {} + +func (x *PowerManagement_IPMI) ProtoReflect() protoreflect.Message { + mi := &file_specs_specs_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 PowerManagement_IPMI.ProtoReflect.Descriptor instead. +func (*PowerManagement_IPMI) Descriptor() ([]byte, []int) { + return file_specs_specs_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *PowerManagement_IPMI) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *PowerManagement_IPMI) GetPort() uint32 { + if x != nil { + return x.Port + } + return 0 +} + +func (x *PowerManagement_IPMI) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *PowerManagement_IPMI) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +type PowerManagement_API struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` +} + +func (x *PowerManagement_API) Reset() { + *x = PowerManagement_API{} + mi := &file_specs_specs_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PowerManagement_API) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PowerManagement_API) ProtoMessage() {} + +func (x *PowerManagement_API) ProtoReflect() protoreflect.Message { + mi := &file_specs_specs_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 PowerManagement_API.ProtoReflect.Descriptor instead. +func (*PowerManagement_API) Descriptor() ([]byte, []int) { + return file_specs_specs_proto_rawDescGZIP(), []int{0, 1} +} + +func (x *PowerManagement_API) GetAddress() string { + if x != nil { + return x.Address + } + 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, 0x16, 0x62, 0x61, 0x72, 0x65, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x70, 0x65, 0x63, 0x73, 0x22, 0xa1, 0x02, 0x0a, 0x0f, + 0x50, 0x6f, 0x77, 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, + 0x40, 0x0a, 0x04, 0x69, 0x70, 0x6d, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, + 0x62, 0x61, 0x72, 0x65, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x72, 0x73, 0x70, 0x65, 0x63, 0x73, 0x2e, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x49, 0x50, 0x4d, 0x49, 0x52, 0x04, 0x69, 0x70, 0x6d, + 0x69, 0x12, 0x3d, 0x0a, 0x03, 0x61, 0x70, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, + 0x2e, 0x62, 0x61, 0x72, 0x65, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, + 0x65, 0x72, 0x73, 0x70, 0x65, 0x63, 0x73, 0x2e, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x4d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x50, 0x49, 0x52, 0x03, 0x61, 0x70, 0x69, + 0x1a, 0x6c, 0x0a, 0x04, 0x49, 0x50, 0x4d, 0x49, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x1a, 0x1f, + 0x0a, 0x03, 0x41, 0x50, 0x49, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, + 0x8d, 0x02, 0x0a, 0x11, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x53, 0x70, 0x65, 0x63, 0x12, 0x52, 0x0a, 0x10, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x27, 0x2e, 0x62, 0x61, 0x72, 0x65, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x72, 0x73, 0x70, 0x65, 0x63, 0x73, 0x2e, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x4d, 0x61, + 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0f, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x4d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x43, 0x0a, 0x0b, 0x70, 0x6f, 0x77, + 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, + 0x2e, 0x62, 0x61, 0x72, 0x65, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, + 0x65, 0x72, 0x73, 0x70, 0x65, 0x63, 0x73, 0x2e, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x52, 0x0a, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3d, + 0x0a, 0x09, 0x62, 0x6f, 0x6f, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x20, 0x2e, 0x62, 0x61, 0x72, 0x65, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x70, 0x65, 0x63, 0x73, 0x2e, 0x42, 0x6f, 0x6f, 0x74, 0x4d, + 0x6f, 0x64, 0x65, 0x52, 0x08, 0x62, 0x6f, 0x6f, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x20, 0x0a, + 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x77, 0x69, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x57, 0x69, 0x70, 0x65, 0x49, 0x64, 0x2a, + 0x6d, 0x0a, 0x08, 0x42, 0x6f, 0x6f, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x42, + 0x4f, 0x4f, 0x54, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, + 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x42, 0x4f, 0x4f, 0x54, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, + 0x41, 0x47, 0x45, 0x4e, 0x54, 0x5f, 0x50, 0x58, 0x45, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x42, + 0x4f, 0x4f, 0x54, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x5f, 0x50, + 0x58, 0x45, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x42, 0x4f, 0x4f, 0x54, 0x5f, 0x4d, 0x4f, 0x44, + 0x45, 0x5f, 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x5f, 0x44, 0x49, 0x53, 0x4b, 0x10, 0x03, 0x2a, 0x4e, + 0x0a, 0x0a, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x13, + 0x50, 0x4f, 0x57, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, + 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x50, 0x4f, 0x57, 0x45, 0x52, 0x5f, 0x53, + 0x54, 0x41, 0x54, 0x45, 0x5f, 0x4f, 0x46, 0x46, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x50, 0x4f, + 0x57, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x4f, 0x4e, 0x10, 0x02, 0x42, 0x40, + 0x5a, 0x3e, 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, 0x62, 0x61, 0x72, 0x65, + 0x2d, 0x6d, 0x65, 0x74, 0x61, 0x6c, 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_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_specs_specs_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_specs_specs_proto_goTypes = []any{ + (BootMode)(0), // 0: baremetalproviderspecs.BootMode + (PowerState)(0), // 1: baremetalproviderspecs.PowerState + (*PowerManagement)(nil), // 2: baremetalproviderspecs.PowerManagement + (*MachineStatusSpec)(nil), // 3: baremetalproviderspecs.MachineStatusSpec + (*PowerManagement_IPMI)(nil), // 4: baremetalproviderspecs.PowerManagement.IPMI + (*PowerManagement_API)(nil), // 5: baremetalproviderspecs.PowerManagement.API +} +var file_specs_specs_proto_depIdxs = []int32{ + 4, // 0: baremetalproviderspecs.PowerManagement.ipmi:type_name -> baremetalproviderspecs.PowerManagement.IPMI + 5, // 1: baremetalproviderspecs.PowerManagement.api:type_name -> baremetalproviderspecs.PowerManagement.API + 2, // 2: baremetalproviderspecs.MachineStatusSpec.power_management:type_name -> baremetalproviderspecs.PowerManagement + 1, // 3: baremetalproviderspecs.MachineStatusSpec.power_state:type_name -> baremetalproviderspecs.PowerState + 0, // 4: baremetalproviderspecs.MachineStatusSpec.boot_mode:type_name -> baremetalproviderspecs.BootMode + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] 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: 2, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_specs_specs_proto_goTypes, + DependencyIndexes: file_specs_specs_proto_depIdxs, + EnumInfos: file_specs_specs_proto_enumTypes, + 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..7af2a5b --- /dev/null +++ b/api/specs/specs.proto @@ -0,0 +1,49 @@ +syntax = "proto3"; + +package baremetalproviderspecs; + +option go_package = "github.com/siderolabs/omni-infra-provider-bare-metal/api/specs"; + +message PowerManagement { + message IPMI { + string address = 1; + uint32 port = 2; + string username = 3; + string password = 4; + } + + message API { + string address = 1; + } + + IPMI ipmi = 1; + API api = 2; +} + +enum BootMode { + BOOT_MODE_UNKNOWN = 0; + BOOT_MODE_AGENT_PXE = 1; + BOOT_MODE_TALOS_PXE = 2; + BOOT_MODE_TALOS_DISK = 3; +} + +enum PowerState { + POWER_STATE_UNKNOWN = 0; + POWER_STATE_OFF = 1; + POWER_STATE_ON = 2; +} + +message MachineStatusSpec { + PowerManagement power_management = 1; + + PowerState power_state = 2; + + // LastBootMode is the last observed boot mode of the machine. It is updated by the PXE server each time it boots a server, + // and is also updated by the status of the agent connectivity. + BootMode boot_mode = 3; + + // LastWipeId is the ID of the last wipe operation that was performed on the machine. + // + // It is used to track if the machine needs to be wiped for an allocation. + string last_wipe_id = 4; +} diff --git a/api/specs/specs_vtproto.pb.go b/api/specs/specs_vtproto.pb.go new file mode 100644 index 0000000..22ac619 --- /dev/null +++ b/api/specs/specs_vtproto.pb.go @@ -0,0 +1,1016 @@ +// 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 *PowerManagement_IPMI) CloneVT() *PowerManagement_IPMI { + if m == nil { + return (*PowerManagement_IPMI)(nil) + } + r := new(PowerManagement_IPMI) + r.Address = m.Address + r.Port = m.Port + r.Username = m.Username + r.Password = m.Password + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *PowerManagement_IPMI) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (m *PowerManagement_API) CloneVT() *PowerManagement_API { + if m == nil { + return (*PowerManagement_API)(nil) + } + r := new(PowerManagement_API) + r.Address = m.Address + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *PowerManagement_API) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (m *PowerManagement) CloneVT() *PowerManagement { + if m == nil { + return (*PowerManagement)(nil) + } + r := new(PowerManagement) + r.Ipmi = m.Ipmi.CloneVT() + r.Api = m.Api.CloneVT() + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *PowerManagement) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (m *MachineStatusSpec) CloneVT() *MachineStatusSpec { + if m == nil { + return (*MachineStatusSpec)(nil) + } + r := new(MachineStatusSpec) + r.PowerManagement = m.PowerManagement.CloneVT() + r.PowerState = m.PowerState + r.BootMode = m.BootMode + r.LastWipeId = m.LastWipeId + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *MachineStatusSpec) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (this *PowerManagement_IPMI) EqualVT(that *PowerManagement_IPMI) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.Address != that.Address { + return false + } + if this.Port != that.Port { + return false + } + if this.Username != that.Username { + return false + } + if this.Password != that.Password { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *PowerManagement_IPMI) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*PowerManagement_IPMI) + if !ok { + return false + } + return this.EqualVT(that) +} +func (this *PowerManagement_API) EqualVT(that *PowerManagement_API) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.Address != that.Address { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *PowerManagement_API) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*PowerManagement_API) + if !ok { + return false + } + return this.EqualVT(that) +} +func (this *PowerManagement) EqualVT(that *PowerManagement) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if !this.Ipmi.EqualVT(that.Ipmi) { + return false + } + if !this.Api.EqualVT(that.Api) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *PowerManagement) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*PowerManagement) + if !ok { + return false + } + return this.EqualVT(that) +} +func (this *MachineStatusSpec) EqualVT(that *MachineStatusSpec) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if !this.PowerManagement.EqualVT(that.PowerManagement) { + return false + } + if this.PowerState != that.PowerState { + return false + } + if this.BootMode != that.BootMode { + return false + } + if this.LastWipeId != that.LastWipeId { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *MachineStatusSpec) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*MachineStatusSpec) + if !ok { + return false + } + return this.EqualVT(that) +} +func (m *PowerManagement_IPMI) 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 *PowerManagement_IPMI) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *PowerManagement_IPMI) 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.Password) > 0 { + i -= len(m.Password) + copy(dAtA[i:], m.Password) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Password))) + i-- + dAtA[i] = 0x22 + } + if len(m.Username) > 0 { + i -= len(m.Username) + copy(dAtA[i:], m.Username) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Username))) + i-- + dAtA[i] = 0x1a + } + if m.Port != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Port)) + i-- + dAtA[i] = 0x10 + } + if len(m.Address) > 0 { + i -= len(m.Address) + copy(dAtA[i:], m.Address) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Address))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *PowerManagement_API) 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 *PowerManagement_API) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *PowerManagement_API) 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.Address) > 0 { + i -= len(m.Address) + copy(dAtA[i:], m.Address) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Address))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *PowerManagement) 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 *PowerManagement) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *PowerManagement) 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 m.Api != nil { + size, err := m.Api.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 + } + if m.Ipmi != nil { + size, err := m.Ipmi.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MachineStatusSpec) 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 *MachineStatusSpec) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *MachineStatusSpec) 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.LastWipeId) > 0 { + i -= len(m.LastWipeId) + copy(dAtA[i:], m.LastWipeId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.LastWipeId))) + i-- + dAtA[i] = 0x22 + } + if m.BootMode != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.BootMode)) + i-- + dAtA[i] = 0x18 + } + if m.PowerState != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.PowerState)) + i-- + dAtA[i] = 0x10 + } + if m.PowerManagement != nil { + size, err := m.PowerManagement.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *PowerManagement_IPMI) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Address) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Port != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Port)) + } + l = len(m.Username) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Password) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *PowerManagement_API) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Address) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *PowerManagement) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Ipmi != nil { + l = m.Ipmi.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Api != nil { + l = m.Api.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *MachineStatusSpec) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.PowerManagement != nil { + l = m.PowerManagement.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.PowerState != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.PowerState)) + } + if m.BootMode != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.BootMode)) + } + l = len(m.LastWipeId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *PowerManagement_IPMI) 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: PowerManagement_IPMI: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PowerManagement_IPMI: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Address", 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.Address = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Port", wireType) + } + m.Port = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Port |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Username", 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.Username = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Password", 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.Password = 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 *PowerManagement_API) 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: PowerManagement_API: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PowerManagement_API: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Address", 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.Address = 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 *PowerManagement) 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: PowerManagement: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PowerManagement: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Ipmi", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Ipmi == nil { + m.Ipmi = &PowerManagement_IPMI{} + } + if err := m.Ipmi.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Api", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Api == nil { + m.Api = &PowerManagement_API{} + } + if err := m.Api.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + 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 *MachineStatusSpec) 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: MachineStatusSpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MachineStatusSpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PowerManagement", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.PowerManagement == nil { + m.PowerManagement = &PowerManagement{} + } + if err := m.PowerManagement.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PowerState", wireType) + } + m.PowerState = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PowerState |= PowerState(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BootMode", wireType) + } + m.BootMode = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.BootMode |= BootMode(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LastWipeId", 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.LastWipeId = 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..b850b43 --- /dev/null +++ b/cmd/provider/main.go @@ -0,0 +1,141 @@ +// 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/siderolabs/talos-metal-agent/pkg/config" + "github.com/spf13/cobra" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/meta" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/version" +) + +var ( + providerOptions provider.Options + 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 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(zap.AddStacktrace(zapcore.FatalLevel)) // only print stack traces for fatal errors) +} + +func run(ctx context.Context, logger *zap.Logger) error { + prov := provider.New(providerOptions, 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() { + const ( + apiPowerMgmtStateDirFlag = "api-power-mgmt-state-dir" + dhcpProxyIfaceOrIPFlag = "dhcp-proxy-iface-or-ip" + ) + + 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().BoolVar(&debug, "debug", false, "Enable debug mode & logs.") + + rootCmd.Flags().StringVar(&providerOptions.APIListenAddress, "api-listen-address", provider.DefaultOptions.APIListenAddress, + "The IP address to listen on. If not specified, the server will listen on all interfaces.") + rootCmd.Flags().StringVar(&providerOptions.APIAdvertiseAddress, "api-advertise-address", provider.DefaultOptions.APIAdvertiseAddress, + "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(&providerOptions.APIPort, "api-port", provider.DefaultOptions.APIPort, "The port to run the api server on.") + rootCmd.Flags().StringVar(&providerOptions.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(&providerOptions.Name, "provider-name", provider.DefaultOptions.Name, "Provider name as it appears in Omni") + rootCmd.Flags().StringVar(&providerOptions.Description, "provider-description", provider.DefaultOptions.Description, "Provider description as it appears in Omni") + rootCmd.Flags().BoolVar(&providerOptions.UseLocalBootAssets, "use-local-boot-assets", provider.DefaultOptions.UseLocalBootAssets, + "Use local boot assets for iPXE booting. If set, the iPXE server will use the kernel and initramfs from the local assets "+ + "instead of forwarding the request to the image factory to boot into agent mode.") + rootCmd.Flags().StringVar(&providerOptions.DHCPProxyIfaceOrIP, dhcpProxyIfaceOrIPFlag, provider.DefaultOptions.DHCPProxyIfaceOrIP, + "The interface name or the IP address on the interface to run the DHCP proxy server on. "+ + "If it is an IP address, the DHCP proxy server will run on the interface that has the IP address.") + rootCmd.Flags().StringVar(&providerOptions.ImageFactoryBaseURL, "image-factory-base-url", provider.DefaultOptions.ImageFactoryBaseURL, + "The base URL of the image factory.") + rootCmd.Flags().StringVar(&providerOptions.ImageFactoryPXEBaseURL, "image-factory-pxe-base-url", provider.DefaultOptions.ImageFactoryPXEBaseURL, + "The base URL of the image factory PXE server.") + rootCmd.Flags().StringVar(&providerOptions.AgentModeTalosVersion, "agent-mode-talos-version", provider.DefaultOptions.AgentModeTalosVersion, + "The default Talos version to when forwarding iPXE requests to the image factory to boot into Talos agent.") + rootCmd.Flags().BoolVar(&providerOptions.AgentTestMode, "agent-test-mode", provider.DefaultOptions.AgentTestMode, + fmt.Sprintf("Enable agent test mode. In this mode, the Talos agent will be booted into the test mode via the kernel arg %q. "+ + "In this mode, you probably want to set the --%s flag, as the test mode agents are probably QEMU machines whose power is managed over the HTTP API.", + config.TestModeKernelArg, apiPowerMgmtStateDirFlag)) + rootCmd.Flags().StringVar(&providerOptions.APIPowerMgmtStateDir, apiPowerMgmtStateDirFlag, provider.DefaultOptions.APIPowerMgmtStateDir, + "The directory to read the power management API endpoints and ports, to be used to manage the power state of the machines which are managed via API "+ + "(e.g., QEMU VMs created by 'talosctl cluster create') Mainly used for testing purposes.") + rootCmd.Flags().StringVar(&providerOptions.BootFromDiskMethod, "boot-from-disk-method", provider.DefaultOptions.BootFromDiskMethod, + "Default method to use to boot server from disk if it hits iPXE endpoint after install.") + rootCmd.Flags().StringSliceVar(&providerOptions.MachineLabels, "machine-labels", provider.DefaultOptions.MachineLabels, + "Comma separated list of key=value pairs to be set to the machine. Example: key1=value1,key2,key3=value3") + rootCmd.Flags().BoolVar(&providerOptions.InsecureSkipTLSVerify, "insecure-skip-tls-verify", provider.DefaultOptions.InsecureSkipTLSVerify, + "Skip TLS verification when connecting to the Omni API.") + rootCmd.Flags().BoolVar(&providerOptions.ClearState, "clear-state", provider.DefaultOptions.ClearState, "Clear the state of the provider on startup.") + rootCmd.Flags().BoolVar(&providerOptions.EnableResourceCache, "enable-resource-cache", provider.DefaultOptions.EnableResourceCache, + "Enable controller runtime resource cache.") + rootCmd.Flags().BoolVar(&providerOptions.WipeWithZeroes, "wipe-with-zeroes", provider.DefaultOptions.WipeWithZeroes, + "When wiping a machine, write zeroes to the whole disk instead doing a fast wipe.") + + // mark the flags as required + rootCmd.MarkFlagRequired(dhcpProxyIfaceOrIPFlag) //nolint:errcheck +} diff --git a/cmd/qemu-up/main.go b/cmd/qemu-up/main.go new file mode 100644 index 0000000..046bd33 --- /dev/null +++ b/cmd/qemu-up/main.go @@ -0,0 +1,111 @@ +// 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/qemu" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/version" +) + +var ( + qemuOptions qemu.Options + destroy bool + debug bool +) + +// rootCmd represents the base command when called without any subcommands. +var rootCmd = &cobra.Command{ + Use: version.Name, + Short: "Bring QMU Talos machines up with iPXE boot for development and testing", + 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 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(zap.AddStacktrace(zapcore.FatalLevel)) // only print stack traces for fatal errors) +} + +func run(ctx context.Context, logger *zap.Logger) error { + machines, err := qemu.New(qemuOptions, logger) + if err != nil { + return fmt.Errorf("failed to create QEMU machines: %w", err) + } + + if destroy { + if err = machines.Destroy(ctx); err != nil { + return fmt.Errorf("failed to destroy machines: %w", err) + } + + return nil + } + + return machines.Run(ctx) +} + +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().BoolVar(&debug, "debug", false, "Enable debug mode & logs.") + rootCmd.Flags().BoolVar(&destroy, "destroy", false, "Destroy existing machines and exit.") + + rootCmd.Flags().StringVar(&qemuOptions.Name, "name", qemu.DefaultOptions.Name, "Name of the cluster (the set of machines).") + rootCmd.Flags().StringVar(&qemuOptions.CIDR, "cidr", qemu.DefaultOptions.CIDR, "CIDR for the machines' network.") + rootCmd.Flags().StringVar(&qemuOptions.CNIBundleURL, "cni-bundle-url", qemu.DefaultOptions.CNIBundleURL, "URL to the CNI bundle.") + rootCmd.Flags().StringVar(&qemuOptions.TalosctlPath, "talosctl-path", qemu.DefaultOptions.TalosctlPath, + fmt.Sprintf("Path to the talosctl binary. If not specified, the binary %q will be looked up in the current working dir and in the PATH.", qemu.TalosctlBinary)) + rootCmd.Flags().StringVar(&qemuOptions.CPUs, "cpus", qemu.DefaultOptions.CPUs, "Number of CPUs for each machine.") + rootCmd.Flags().IntVar(&qemuOptions.NumMachines, "num-machines", qemu.DefaultOptions.NumMachines, "Number of machines to bring up.") + rootCmd.Flags().IntVar(&qemuOptions.MTU, "mtu", qemu.DefaultOptions.MTU, "MTU for the machines' network.") + rootCmd.Flags().Uint64Var(&qemuOptions.DiskSize, "disk-size", qemu.DefaultOptions.DiskSize, "Disk size for each machine.") + rootCmd.Flags().Int64Var(&qemuOptions.MemSize, "mem-size", qemu.DefaultOptions.MemSize, "Memory size for each machine.") + rootCmd.Flags().StringSliceVar(&qemuOptions.Nameservers, "nameservers", qemu.DefaultOptions.Nameservers, "Nameservers for the machines' network.") +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..dd5ac83 --- /dev/null +++ b/go.mod @@ -0,0 +1,159 @@ +module github.com/siderolabs/omni-infra-provider-bare-metal + +go 1.23.3 + +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-20241125223356-d6063fcdb22f +//github.com/siderolabs/talos-metal-agent v0.1.0-alpha.1 => github.com/utkuozdemir/sidero-talos-metal-agent v0.0.0-20241118155617-c07abd7f024c +) + +require ( + github.com/cosi-project/runtime v0.7.2 + github.com/google/uuid v1.6.0 + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 + github.com/hashicorp/go-multierror v1.1.1 + 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.7.0 + github.com/siderolabs/image-factory v0.6.1 + github.com/siderolabs/net v0.4.0 + github.com/siderolabs/omni/client v0.0.0-20241126230020-5a26d4c7ac9d + github.com/siderolabs/talos v1.9.0-alpha.3 + github.com/siderolabs/talos-metal-agent v0.1.0-alpha.2 + github.com/siderolabs/talos/pkg/machinery v1.9.0-alpha.3 + github.com/spf13/cobra v1.8.1 + go.uber.org/zap v1.27.0 + golang.org/x/net v0.31.0 + golang.org/x/sync v0.9.0 + google.golang.org/grpc v1.68.0 + google.golang.org/protobuf v1.35.2 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + cel.dev/expr v0.18.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.1.3 // indirect + github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect + github.com/ProtonMail/gopenpgp/v2 v2.8.1 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/alexflint/go-filemutex v1.3.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 // indirect + github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // 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/containernetworking/plugins v1.6.0 // indirect + github.com/coreos/go-iptables v0.8.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.3.1+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/emicklei/dot v1.6.2 // indirect + github.com/emicklei/go-restful/v3 v3.12.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/florianl/go-tc v0.4.4 // indirect + github.com/fullstorydev/grpchan v1.1.1 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/gertd/go-pluralize v0.2.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/cel-go v0.22.1 // indirect + github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-getter/v2 v2.2.3 // indirect + github.com/hashicorp/go-safetemp v1.0.0 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/josharian/native v1.1.0 // indirect + github.com/jsimonetti/rtnetlink/v2 v2.0.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/mailru/easyjson v0.7.7 // 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/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // 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/v2 v2.0.6 // indirect + github.com/siderolabs/go-cmd v0.1.3 // indirect + github.com/siderolabs/go-kubernetes v0.2.16 // indirect + github.com/siderolabs/go-pointer v1.0.0 // indirect + github.com/siderolabs/go-procfs v0.1.2 // indirect + github.com/siderolabs/go-retry v0.3.3 // indirect + github.com/siderolabs/go-talos-support v0.1.1 // indirect + github.com/siderolabs/proto-codec v0.1.1 // indirect + github.com/siderolabs/protoenc v0.2.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-20240224005618-d2acac8f3701 // indirect + github.com/ulikunitz/xz v0.5.12 // indirect + github.com/vishvananda/netlink v1.3.0 // indirect + github.com/vishvananda/netns v0.0.4 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect + go.opentelemetry.io/otel v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 // indirect + go.opentelemetry.io/otel/metric v1.32.0 // indirect + go.opentelemetry.io/otel/sdk v1.32.0 // indirect + go.opentelemetry.io/otel/trace v1.32.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.29.0 // indirect + golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect + golang.org/x/oauth2 v0.24.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/term v0.26.0 // indirect + golang.org/x/text v0.20.0 // indirect + golang.org/x/time v0.8.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gotest.tools/v3 v3.5.1 // indirect + k8s.io/api v0.32.0-beta.0 // indirect + k8s.io/apimachinery v0.32.0-beta.0 // indirect + k8s.io/client-go v0.32.0-beta.0 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20240827152857-f7e401e7b4c2 // indirect + k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/knftables v0.0.17 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4a2a9f0 --- /dev/null +++ b/go.sum @@ -0,0 +1,574 @@ +cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo= +cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= +github.com/ProtonMail/go-crypto v1.1.3/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.8.1 h1:WGE1THOhOnLurL0+N4BOlLkIhjEO7YVZgmpgyDHN56A= +github.com/ProtonMail/gopenpgp/v2 v2.8.1/go.mod h1:4PUgqGSQjd7HldUbAgMmC69+Gv6DO8NomCNi0y8+BTc= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/alexflint/go-filemutex v1.3.0 h1:LgE+nTUWnQCyRKbpoceKZsPQbs84LivvgwUymZXdOcM= +github.com/alexflint/go-filemutex v1.3.0/go.mod h1:U0+VA/i30mGBlLCrFPGtTe9y6wGQfNAWPBTekHQ+c8A= +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/armon/circbuf v0.0.0-20190214190532-5111143e8da2 h1:7Ip0wMmLHLRJdrloDxZfhMm0xrLXZS8+COSu2bXmEQs= +github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= +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/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.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= +github.com/cilium/ebpf v0.8.1/go.mod h1:f5zLIM0FSNuAkSyLAN7X+Hy6yznlF1mNiWUMfxMtrgk= +github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok= +github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +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/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +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/containernetworking/plugins v1.6.0 h1:lrsUrLF7QODLx6gncHOqk/pnCiC7c6bvDAskV4KUifQ= +github.com/containernetworking/plugins v1.6.0/go.mod h1:rYLQWMJz/dYuW1XhHdc9xuzdkgbkWEEjwOhUm84+288= +github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc= +github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= +github.com/cosi-project/runtime v0.7.2 h1:b8/v/YpP75LNYLyP5x0+EdqPWtNn6sfJggGGzkqZ0H4= +github.com/cosi-project/runtime v0.7.2/go.mod h1:EMLs8a55tJ6zA4UyDbRsTvXBd6UIlNwZfCVGvCyiXK8= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= +github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +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/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= +github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= +github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk= +github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/florianl/go-tc v0.4.4 h1:q6lhEWEfyhGffRzdl3eIcNqX/yVIw0IJwXqa9Rdcctw= +github.com/florianl/go-tc v0.4.4/go.mod h1:uvp6pIlOw7Z8hhfnT5M4+V1hHVgZWRZwwMS8Z0JsRxc= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= +github.com/freddierice/go-losetup/v2 v2.0.1 h1:wPDx/Elu9nDV8y/CvIbEDz5Xi5Zo80y4h7MKbi3XaAI= +github.com/freddierice/go-losetup/v2 v2.0.1/go.mod h1:TEyBrvlOelsPEhfWD5rutNXDmUszBXuFnwT1kIQF4J8= +github.com/fullstorydev/grpchan v1.1.1 h1:heQqIJlAv5Cnks9a70GRL2EJke6QQoUB25VGR6TZQas= +github.com/fullstorydev/grpchan v1.1.1/go.mod h1:f4HpiV8V6htfY/K44GWV1ESQzHBTq7DinhzqQ95lpgc= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +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.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +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-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +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.22.1 h1:AfVXx3chM2qwoSbM7Da8g8hX8OVSkBFwX+rz2+PcK40= +github.com/google/cel-go v0.22.1/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8= +github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= +github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= +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.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/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.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +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.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI= +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-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-getter/v2 v2.2.3 h1:6CVzhT0KJQHqd9b0pK3xSP0CM/Cv+bVhk+jcaRJ2pGk= +github.com/hashicorp/go-getter/v2 v2.2.3/go.mod h1:hp5Yy0GMQvwWVUmwLs3ygivz1JSLI323hdIE9J9m7TY= +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/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= +github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +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/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +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 v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= +github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= +github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok= +github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw= +github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs= +github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA= +github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U= +github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190/go.mod h1:NmKSdU4VGSiv1bMsdqNALI4RSvvjtz65tTMCnD05qLo= +github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786/go.mod h1:v4hqbTdfQngbVSZJVWUhGE/lbTFf9jb+ygmNUDQMuOs= +github.com/jsimonetti/rtnetlink v1.3.5 h1:hVlNQNRlLDGZz31gBPicsG7Q53rnlsz1l1Ix/9XlpVA= +github.com/jsimonetti/rtnetlink v1.3.5/go.mod h1:0LFedyiTkebnd43tE4YAkWGIq9jQphow4CcwxaT2Y00= +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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.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.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +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/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo= +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.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc= +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 v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= +github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= +github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= +github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= +github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8= +github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU= +github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU= +github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys= +github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8= +github.com/mdlayher/netlink v1.4.1/go.mod h1:e4/KuJ+s8UhfUpO9z00/fDZZmhSrs+oxyqAS9cNgn6Q= +github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA= +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.0.0-20210307095302-262dc9984e00/go.mod h1:GAFlyu4/XV68LkQKYzKhIo/WW7j3Zi0YRAz/BOoanUc= +github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= +github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= +github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= +github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +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.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +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.7.0 h1:uHAt3WD0dof28NHFuguWBbDokaXQraR/HyVxCLw2QCU= +github.com/siderolabs/gen v0.7.0/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/v2 v2.0.6 h1:/NAy3MbNZhjLWo28asZyS/hmf86PEPDMc9i6wIcgbwI= +github.com/siderolabs/go-blockdevice/v2 v2.0.6/go.mod h1:74htzCV913UzaLZ4H+NBXkwWlYnBJIq5m/379ZEcu8w= +github.com/siderolabs/go-cmd v0.1.3 h1:JrgZwqhJQeoec3QRON0LK+fv+0y7d0DyY7zsfkO6ciw= +github.com/siderolabs/go-cmd v0.1.3/go.mod h1:bg7HY4mRNu4zKebAgUevSwuYNtcvPMJfuhLRkVKHZ0k= +github.com/siderolabs/go-kubernetes v0.2.16 h1:sTvxsqx07zgPZRz2/vXKXRWV304biQ0JMnHckTVWrtg= +github.com/siderolabs/go-kubernetes v0.2.16/go.mod h1:ElwMMmkNLrSRdAMqOouN9RZbO4G3CM5fJ0F5CBRsGRE= +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-procfs v0.1.2 h1:bDs9hHyYGE2HO1frpmUsD60yg80VIEDrx31fkbi4C8M= +github.com/siderolabs/go-procfs v0.1.2/go.mod h1:dBzQXobsM7+TWRRI3DS9X7vAuj8Nkfgu3Z/U9iY3ZTY= +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/go-talos-support v0.1.1 h1:g51J0WQssQAycU/0cDliC2l4uX2H02yUs2+fa5pCvHg= +github.com/siderolabs/go-talos-support v0.1.1/go.mod h1:o4woiYS+2J3djCQgyHZRVZQm8XpazQr+XPcTXAZvamo= +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.6.1 h1:3K+IJ0Lkci3dewKhvbyI5d5oqixzeozBhuLY8JCdBGM= +github.com/siderolabs/image-factory v0.6.1/go.mod h1:lzQlB491okoHx1nXfuR3/0Cd8zMFioYdcBkl4Tz+fs8= +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/omni/client v0.0.0-20241126230020-5a26d4c7ac9d h1:tfBybmE9URYqvnowPj7WLlX7obwAC0gvbVCn36WOK6Q= +github.com/siderolabs/omni/client v0.0.0-20241126230020-5a26d4c7ac9d/go.mod h1:m4JfMpvdi48YkQmniQvCiEkNtqWM+fRIiHaxpKJUzwc= +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 v1.9.0-alpha.3 h1:8Bod2XkarNNp/+sOC8a2Btbv0Z7ozLvATfpy2m7hm7I= +github.com/siderolabs/talos v1.9.0-alpha.3/go.mod h1:3xj1nrnwm3I7ZKBnILgf0K9h8UztqbxjJ7nKMDRgrH8= +github.com/siderolabs/talos-metal-agent v0.1.0-alpha.2 h1:r3p8ibJWGQYnxixtkDB9cHSzkQW7lRE/O04nTMpVTTo= +github.com/siderolabs/talos-metal-agent v0.1.0-alpha.2/go.mod h1:sG7GnlIW1TTGE0mc3Cv5d/sUSzUkv0CsaG+qcv1Oy24= +github.com/siderolabs/talos/pkg/machinery v1.9.0-alpha.3 h1:KRvVtqUGaRcQ669N9Pf8fKAguYHMKVCsF4iA4Q4Lm/k= +github.com/siderolabs/talos/pkg/machinery v1.9.0-alpha.3/go.mod h1:3pZ8wRTnS2mKU3N5PLVOvcu0ktZ32WUDVUcC2FWOXAs= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +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-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= +github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= +github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= +github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +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/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk= +github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= +github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= +github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= +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.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +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-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= +golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= +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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +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.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.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.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.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.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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-20241118233622-e639e219e697 h1:pgr/4QbFyktUv9CtQ/Fq4gzEE6/Xs7iCXbktaGzLHbQ= +google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697/go.mod h1:+D9ySVjN8nY8YCVjc5O7PZDIdZporIDY3KaGfJunh88= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +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.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= +google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= +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.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/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-20180628173108-788fd7840127/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +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= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +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= +k8s.io/api v0.32.0-beta.0 h1:LW+CrsFQoKZOyLUa+Bf41tt54+JGlpT2+DZkGYqOlBU= +k8s.io/api v0.32.0-beta.0/go.mod h1:33Wz5e2udOIvYDiwVjLOhwztCGp6NgU8aek42yZ1jjA= +k8s.io/apimachinery v0.32.0-beta.0 h1:xThnRQcnBNOC8cI6hsQenJWJ85TV0Eqw5QQWL8Zmz4k= +k8s.io/apimachinery v0.32.0-beta.0/go.mod h1:RBz1atosgwQyw4A8TzwjTQDnBVo/eak+3xLfOQr/By8= +k8s.io/client-go v0.32.0-beta.0 h1:fCqEOwDI9WcckKyv3Qodo+uOLxIeZzF3ViIL7L/kJn4= +k8s.io/client-go v0.32.0-beta.0/go.mod h1:oABdYo0CY4EfVQziPNjR5IejpTIZPWSl2rZY0wdc3lo= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240827152857-f7e401e7b4c2 h1:GKE9U8BH16uynoxQii0auTjmmmuZ3O0LFMN6S0lPPhI= +k8s.io/kube-openapi v0.0.0-20240827152857-f7e401e7b4c2/go.mod h1:coRQXBK9NxO98XUv3ZD6AK3xzHCxV6+b7lrquKwaKzA= +k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078 h1:jGnCPejIetjiy2gqaJ5V0NLwTpF4wbQ6cZIItJCSHno= +k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/knftables v0.0.17 h1:wGchTyRF/iGTIjd+vRaR1m676HM7jB8soFtyr/148ic= +sigs.k8s.io/knftables v0.0.17/go.mod h1:f/5ZLKYEUPUhVjUCg6l80ACdL7CIIyeL0DxfgojGRTk= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/hack/certs/key.private b/hack/certs/key.private new file mode 100644 index 0000000..1fefbc9 --- /dev/null +++ b/hack/certs/key.private @@ -0,0 +1,106 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +lQcYBGMzVUsBEAC5Ioyi/Rs06GL3zeKVgmi9F8xF6yDjec+E83FFyUdO1xiZ4fDf +WQhUKh5c4gJi1hoGOWPkFWsEx1sTPmZsAdYIHmOSt6qPGbdt22JnfGseYdTRpMmi +blkcbvUm3uidghDd5HiacmhaZqh+5AjN0T61QVZfryo5gb9y5wMxCVp2d8t5PG1/ +ULosQ6WfhQhCkyzF1eiuLyxfCToyYUhRRhveRsitrNnpAaNBjOvtG9ujdRz3EPsn +q0kcwDscqqUdZIPNNe0LyUk7PDy61eTEYz6wSZVVw9l7Gng0cIs5ws89xa2Q1AFC +9V8XE5/755R/j4V4+Iu4oqKhOF9U5Ic85LJ55wbu8mowgPsC2hK+09lqNaHzI7qf +2pmS8Vd/6ABLCOILOFuXgvZtx7tV124GPhS72jOabkjegmFA22Gfnnrv56KrWnSv ++nk1R8FTaggC54JxsdWYckyb3bcE8J9R5T1fU+54sspEbiGQpzMALgk1AnKnlVsV +kcXO7NvsN5xZinskRXfi9YdtwLuzZegDuq/8/dByatwKHLzn5QEQLw9HQlhQcnJB +5GqCUl/ljncl6CmXpLZbDJSaBTeTeeDMe/a508IoMiIe0s7VBFGFZOaW5pOMWZo4 +0nVE91v2oIEBvF3IKVNlEWDCekiNnc0G4K1VMLq3Kq+OgTnjFnEZCM1M6wARAQAB +AA/7B4h1sIFxWfXFZnqPfbCQvSD6xDLiY7R70oJqrtbseojGDNI6EDHPOkgnHssT +eHITHQMOIs8RoQrQ6eI1c1pKcIohWGxLbwcyL4YoZY2VI5ICLDBIWWj5Ye7Mit2x +xAxGzhXofQsACVhOrYXEgJttsce20ViPSfJoQoSj3Jrvfg03JPe7J4hhYEELEYft +jh1ESnMpxJdRduHT5w44+Gr3N1QqAOcc9sjaRmXgM7BZKjgvCt6Qrvcz7QzlWtqW +srDPAXVFmp/WGv2YewG5DUSnMwUgznHpp4NW4MteNDt5CJ/ChYzFgF6mP993OF4e +l2d/nCyA2EJwhkmE7NoKUVqnkxHPuUXYgLjc1ca+rtaSScbmlwIOJKRDVD5hsZEw +TKImUsaJ/PVOmCN8MwW5yXUx3ZvB6JlFSOjOT267B/Jk7LM3zupKuQJt8M4o6QZy +vkMAUxZau74jsTcXB6fB2NCSmaFOlPi9rMmOgqiNpdzzaSOVuJfI7lHgS7UDS2yn +oaWSoPUxVLEmNRTFbP1ArZ2aEUaCy/ThvQ7aFJlDbBbQbmrxz7hMp0oiWJVf2X+2 +VUEuTZAlTNYoXCSMVR3UoJfKJ99trNP7kuNiiOt5puDrhFDwNKxdjm4qRPBw6tsZ +cBUFmnbx9S6tGNGM9BMhGZW87YMHrMmPRwGHZrBXiaV99gEIAMpFtVIXP2RTL+T6 +DgjmTz15pf1WuGBZcIg2DUs9lNRe///E0hv+IxNY1rY5g7xmJRpZfMNDCFvtEWen +jrCJ5vJBO1Vxky4NsM3t5wbqJXYam2lib39HAXKuMTIGjXh29ShM57++FBTITXiO +pgdO3tg2FwIi/Hc5wqYwRYpJ+gVn6rDy5mR1jD99z5ccIKAPEPWw4qQ2+ICHhICu +Oe7aU3OTtFaS/RS7Vo+bXxENUb26bTyQMrztxHgIPnU01DamZHbhwq7uEustZ9+c +DBMuo98XOOru15XJAaQnPd5Qi2Nz8Cqttfu7QoVBxwcWxvNU6QuYxASeGOrtYfAf +5nfvsx0IAOpPhWEtcoRQZxf/EQ5s7YsQ8ztmVVOY6vNQC2x0EqRbJaJfI62KdQBB +nao3Je6syKRAGQLHKcoc3dDuKT2aFPoRXFyptSLK83E2ktlIa71RPI3HcEoduqm7 +H6MPfxKXWvKF09SV5e1Bj9Dqpe8NuE81w4CkDlaVfvdoBm0HRa0c1oZ5zDNZ/Jxk +ogPBoLWJ/WJbOo8O3ddtYILJG2mAOeIztslGu3Slsaz3Uk7WaG+5hrc783/K9yDZ +7J5cP8hgv/Z0uVJEy34mY5s++dce3uO5OilV3QosehrV8PLt2lXOwcxNesy5QqWf +1fDW0jb1znP+o6uQ5J0blmwekxtmOacIAIen0hx4LbhHEvWvK7AAKvct/OmVgJxB +n1IxsaJV6c/qKEqVbf5g7cg/0I9hlsCO1B/1eqrXBD7QDjB3/3lSr3WlIOHKf4Bf +re3v7kmXEGN1RZzyO2YdI4mLpYF3GYrQtMwo5pD6QPZudW+uRbZk5cAYIO7pb5uZ +pbJGK5pU35UE+wkL6/oWTjn7dnBpKtamGlZ08ssl7HDPZ/A51MGnyNjYCEMC/3IE +nmhfaeQklKI8uF6VBy0ixueTXSRbb3EO7Pj9+tF3bVRIESBfvujaH++TAJ9h89gi +UFIwBtNJvXGj5DA2nF46y/PurMQKMr7djUA7BLBu7Usj0x9jWAv0Z5iF7LQ2RG1p +dHJ5IE1hdHJlbmljaGV2IDxkbWl0cnkubWF0cmVuaWNoZXZAc2lkZXJvbGFicy5j +b20+iQJUBBMBCgA+FiEEozmFFCsEH0WF59oCIBBLTR3n8ckFAmMzVUsCGwMFCQlm +AYAFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQIBBLTR3n8cnGkxAAkdDRmP++ +K2q3s1gIGMd+ENzRplyDPxlUrQTKuopgR9xf1xRf6vdZoBT4D3sDtY7s7LBakxk9 +u15+Og7V9w8L+3i620W0KRYS1cOTjLN1BP7inaiqGCHF/KUFHlHIF08Iz6xv3X4P +/+3oDrB/6TGqdU+DUThjA/ugUt8eVvY+VNg/8d+A4M21YOU/rjTf/PpN9FbXDbdZ +3i3gfue9lNHJxK5CLv8sDltUFH2KF8pMgobkQ7WObwmR2hHcBaFtQAgxKV2pb1La +jnZQAHWBEkNpJGL5R4AoApfLupcv/AASmdwLkBiwP/PzvvxhSKUMbb/LQKxK+M+3 +HLJ+lTHlNs92lU4umvpZ5W1AJQ8nLR1GKn3ufm8cD+hDzekroO1y8MTnzxKX77rA +RjEQt//6GTuRkRrQcd6zjcAOnl3rMpEtuneWFnUWghBSuz82IM7WNGFTSVKRNthp +NzKlu45rgBmERzn6IXA/l/r5Q4g5uM48FvJUy05znWftEjumuzG5m+1eB2Zu7xwd +7icW1/mgL14XDvGnlnlsm6BQRR6eN5Vrx0TADFuJQPpilsouhBYuz1p02hcxMkGo +Svjcwphc3w+yc8610eosBUbVV5KbX68wC+aSFIa4L5CS9fl68JGkREeEKA5YCCOj +fgB8JX/mfSkeoSSZg0vGxBDajsN0G50O6BGdBxgEYzNVSwEQAKIkCwr/fw9VWLbm +bCYckMEkF6wcXssO6ymoHwk003F1ZUsR8RQXsey7TWj6sLmS79tW8/M5RGxHdsZm +SBxymFbdOo5c2nup0jxngV5v0IkkqWidryKOCLctK6l65qjUBhDzqLy4jRGcnf+2 +Z9XFjy+BbzY7FScT2rq4Zj3af4Zu+xfIIQ/JO9Sk9E/k9sKLNd/7BXWxDR5cPQs2 +K7WjK0ax6vND1xYLU8wm6ct+Z0dpMNA9xbxRM7uTLR7P3oonO4OVjNBHYWgpKYVE +YF9zcevLByfgMAagGIsG3QSqdgv9LNKwv68LR7jP6JNFcdaQ1FW1e14QLECjs+xl +RjgY/hoaCsCg7XXpbj5aJr5Me9hIbF0M1toMjujws0VGjpv1CNYuQv16efzZytQK +GmIm30Tj5nHY7L3TwDiER9wCdvnjjX8+gIqM82mu7jElpLWFE+xg6IxJgRGcmOd1 +PPQY+5bCGVKCUHLNCJRPtYC/cXmjJUTDmLJUzdc4ysWLrdclSnaOHvv4T/VnVzq9 +EuibZ3xu+/8a/amxnjq3Ck+pzKbtdpCycqYJSfnXhaHcY8I8M1mKYg721cKy5eAV +D+zkRm7oJz0CblB9Ds8+s3O0KLRosGSHRrBcpNfEUs/qs/NeuCx7DxSmnA/9Ohrl +P3SrEpBodv8dNKgKry18VjjMBUMBABEBAAEAD/0TPebHl9ma1ryP/BlyjmpJWYCr +rrQ7MdqLl4WTYJ8FNHLgbVEoWsWFPBcsMa/+Xec0JwYNY8rwdKyuT94X7iuRB3EX +CwLssRMfkwMB05AyblTicvAhUCzNnEE1vD2aZIsRwPDR8K7hG66OdbWt42OiNiCe +FXXlrNAE37RWe9Mtf4cx49C0oGOG0UqjHp+AJ+gtXAtiU7AkXbrq1TNru2D740po +MzFXzuFTdXzCZw5Xpa6iz+ni9toGVSmCIhYdXBmOfJV49Delllj0lVBAk6E949rG +Cy934dD30skw8A/RTWrf2BTvb43D63yE2bVwSsDAKSjqWU3/H85O7BfguWqSOw/b +d9c/1PzY6DnmBjP6a6UqQ207mMmQwm+kUzOrIoPeGrwrksAperHNvNwM0uFymVkx +87tgMI2amGXhvo2FjeYhpAmXIygMmXGmNvFKW0DqJiUzbEYM7RC+A2+1fw2eSuqN +YOEhjA8jvRkKjIUhB5/X10GYp/vlV8Kaomhcxfk12VnwtEblwOHoeIsAREnN7wzs +mG50sV1NdaJy4B6JwE5gJU6i9h+KcYrs49vqOIJSGJ7nDRwweMzxhEvl5PuaZJy6 +FLuslHvgRT6GWNE/eIAWavgYmj5a25Tx80i2Z+ztbtKT984eZ6VxKXa+JlOkAcXC +0REKPz0h7pm5NvEafwgAyXMhkjNKNpB6C6HfQ1/lwo4d4+LN/C9+3D/BeTSI56aa +vVzXK/EeF0cvwsbaTn+M9HoF/MhGKftk+TSsWXyp/Yp9se4LntyOPBHspRI/mXj7 +8kvcZCBBfLoCe8L2ytM3a7c0NZsJ8qFD3qNZgaYELyH59c7bCWhczLLbg0QSXZHl +H2l3LPdBDcrgY1CthWOtd2kKPPNaW7B6/8Mk2eymHFVmFfVJ/6jW0Y6HJjgsF5L+ +a2zx/t2sAfB4WIBlAXIInZXWtcWHb2ArJDzHiSjhtzIRuHG5uG+48MbuYAO8zK5j +5Y3UscHbZ6MQ0rDMKbFJawOhtuOW/dCQy2Zsj6NQAwgAzgvxjblw3NxKAcQ94M3Z +8o1cJ80oH6xNqEceqHhlGr9QLKPIK9FLBgO3zv6NqDWrbyA2TxKMK/09EfaYKzcr +VP8e53Xvl27lzQH3jy+4ii7ZF9GlVLtn/hw/CrNQYBOO9whEEoe95oKkBc6B1tRP +uUVrpnTfY5qgPKn64pwflTZ1iNnSmW1GkUkHeJn3Ckts2dK5tOC3lbgRzyUMzq0r +Ze+pxi8tLIUOxw05yngUU7Xpf6nDlhZJLxO1vQIBJolwTpRnrudQftBnZz7guQKL +p1IWeNb8QkCjMq23kSaeyvAlpOzoIBm1ePmcavA7fLJJqLepV2C1JCDZTByNbX+b +qwgArquLk/d1sShSfA6LqESxaJGYXAkYf2kXcJSIR3A0z7m91swL+Qrs7G/g5BfI +8T5xtErmzK3ex4/ZYYKYCIUU/xDO/ZL+Ks7u2aPJSQGQZSPRNLk0LJK1T132FB+z +j9WPqqu2mfr7T5YO5vMJDcd6gDEFSqZzS/8aogWwZwoIQD6DBCBCJvOodDyncgzV +cMQytTQC+Qjea5Ji++1NHA7i9Pyj70HgqmyL6T90Lrm8BurA+/IBWgOYFf2aFrVi +6tgxryIYjEHNrDvEBU9SVUHco3s1+7dAm86DrL5K3rLwbDbRlz5SjlJdL0+NLG1+ +wDbzwdI0AHuhxc/PZHxjRhm1DY5IiQI8BBgBCgAmFiEEozmFFCsEH0WF59oCIBBL +TR3n8ckFAmMzVUsCGwwFCQlmAYAACgkQIBBLTR3n8cn5ghAAkXuf1KDd8I/hPxH/ +C2b6u4wjrTWaaSvzLacAjCwOf4ctHyApb7SbiQQ0cFD/0c3Vedf13O0xJJ8BdSO7 +cdF/RpVvR8hE8ebGoylnkWhKKGmB68aw7LmFYLE2pwuhx8GFuAsVz2eozrbzRqyk +R7o/HnMSoJ41zlPLI4Go0NjETycZyzSKfNQw+5KWKbOnU7kGvCZReEuNGpyjDZ9I +HbnB+qlRyY5QCcX+nEX6sWJyIoNyOJbu2D3XG7ylQGZL71nogTRhLMBL7VDUaR3r +/s5l3E16jn5cn798p8o4bnFXukY9JOi3V8yL+X2Kg/ylwJYmnhhyG9c5Bu4I9g0F +Q+Z+rbN4YoG1bt112rAA1KlekhuJwdDAn9QhKx0Nod13wcHFrjKp/Z1YH8TYxcUc +pjeg0+BMs/K4PyO9xvnGEuITcJBoSnQRx/E+gShu3DL5ndmFsM8qyx7fJRXsTtDb +21sArBHBNuUa5blAp1fVCm4RQ9+F1GOwnOFRJfWVrhp8ninHtnM78TnDd1wczLVv +SNVAXjsHoZTVuuCWWXPVX0OG22fPAy90nJpxGtPNVFI5se39Srk/1Yg92vhO/G3T +UtoMuMxSBCD7BD6ZFox/ga3fARvfreZuiNQn9t6J6d7DChqCQxo2Rtk9WWdy72W9 +hp/KSDr6GKR6H5ggj1v1tBWE95A= +=pdI2 +-----END PGP PRIVATE KEY BLOCK----- diff --git a/hack/certs/key.public b/hack/certs/key.public new file mode 100644 index 0000000..dd080ac --- /dev/null +++ b/hack/certs/key.public @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBGM1pZcBEADpbAWrOBHK3WDzwoxAgrzcoMEG6YSvKraBV/622SVeiwTfiz1Z +Bij62jCTXoU0m+FpKXahbcTpcNoF8DFGkCJO6bY66a5IqccgezfZt/augByWju/6 +9PJy9qCD5V//ASH+3Xh3gMyhnVOmkddAmfoGtgm8eE+bLTGW+2jQFh0blK0np5Ad +ycWZJtKYWYrCV7KEUiaqfUgLFyyrc+ssIlqboLHHUtfsqZAUOYcSHv9DIvyVGNE8 +TOgir243KybNOUlbqmE+ubUQHKHOSWVAAP15XpgYPnZcJFlcBacbN+uY5SZ62sxl +yNxrZ5wQepo+x5g9l8z9HbTKG7pVwgw4lz3r9msj5WRFhm/mqb123DB5yPwMZoI1 +hFTlE03Vma/kDZY0li8CCYE+kWTcy+GKt7biK74z83OUeTDeIwfLaj7VaRSDNelB +xrooXMCrH2g8nSsFbkQ3mXjYnSYjTPXMAXeO8BFXMf68sjHsz49ychuY8A6QG7+8 +4nbsV0761w/kUdOF7BtmKcx0usXxYfTPW1YsCYbI/I2QecYxL4fXK07JtZi+DPGv +lQZvSSN8FUMER154ABSwA8CGYIZKHVGw8ugkGg+YCLkE/MfmyDkuFWgzvmiI5Yv3 +hqUcDVSbfyJPxORjPaOyDe/5uM8u4mjJ/p7HEbR72piCrAA/fCnB2Wyt2QARAQAB +tChBbm90aGVyIEtleSA8YW5vdGhlcl9rZXlAc2lkZXJvbGFicy5jb20+iQJUBBMB +CgA+FiEEWEqvBW4aEnjEeEUAjYzoQ1t0ggAFAmM1pZcCGwMFCQlmAYAFCwkIBwIG +FQoJCAsCBBYCAwECHgECF4AACgkQjYzoQ1t0ggDDSw//SsVTfIGi6cHmdFOEolBS +7ewvcwCnY/HKeR3YPROFFvr5jdOyWY/dVJKY1x9MazDJx72NbErReEHZ6azxOrGL +r7PDOLvChzQzqHZGncma5G69AcPehZJ8LW/OdCFK5j4gOCFb+KxvMpKjT7TiiWF0 +zSenJL78Tx9D9LdYy45ANnPFrprIkeq63GUnjbVUAeK2laaqp3Q7V7sALCh8uHyw +hQUdg8/hhh2jyIARWP62+8FE6GfKGbiBAiw4ff5YhBzBL0NWB+9HrKHEAW+SEpGq +2zq1Y8oNEQIKEDMuXiwauT29b2o96COGo+N8+rTDGBVsDzBqLR4EwH/rsDZzz2xm +HS3xCax4JWefIgLV3Cj8x/pxmH0UEiy9m5QRMbeVITgXBL6BnxboIE8fxzn9n9/q +IZgemICLUUMWU6pR2rpCMN2u3YnNd7aPhKObUeo6QDN5Ya59IAruXYmkn/XNWSI3 +ySHpldxDE8NE+JUw5+JWfAS4AhTKIoXUcK/IEiF2ASbiACBVYdEZybfHOBQUQq6f +NA7QLrZ5p0yW6TMeMeldgjN4ByFcKm9dTj9sy7pjW2LIzVDHKvV41CLiz57HhQS6 +yaIxVgT/MIFcS975sW/hK/pKFN979+Wl0pjV+/IUPZgjGb/g40DwLsqoKaRkBG73 +zUVmE/E9cMBA0h3pSMI8V265Ag0EYzWllwEQALmv4nDSosRnUumKHNi9XFKKFsDr +6DvDodzy0gho75CoaYOlvhu5pliIrt30fhMQw4dP+tSz9JgaK9CtroJ2BbKSgFAE +9LtMsnBtYE0+wp76xJBoGt68znGbnZ+lwfRkENe6ia6DhUUbpyjdg+Fks2EVLALP +REmRTUVefsL8DfoPTjS5gPhO4pk1afUgz+5oumMnnSoKyFXo4G/1CkK+i2u07NTE +A/AB/o3lOf3vkH1RGhKuz5s9rus2ADamX93Dr/TjpGFG2s08wYJC3Z/FeHcMTxLf +hJSuVi2z9gIHJnyrxL/OUh5OXZBYy5g7czN35bzGaFUmbFNBxkWU/NUubloWtIIK +JLuCQaTS2p6oz2A478HXkOVp9GUpff+y6CXdVvjeLqmNPJDYsQCJMUy7ed8yThYL +EFhO7taUx6T+wXLyLxDpaKAUrE67WSuYCFy57K4aANkz5vwJUBsx5KDLrLDyCWd8 +UYS/CtMl8k7wHWl01VwFq9jx5YspVgo9k9eFWTC9ARS5c5Olmcz224BaR87lBQmj +gLbX7DKhRMZe9ickwHoB3vDVuW0c9Mv9hu+1lcXMyoV56T4oHHQl7D+Fk/m786RD +dL3U0V01qjr4cWK5WKemX16atnROAEcgWbSqBoJKUo7dRnQKeqBFJK/3fXc0PZcP +VB3cotiL9lOEcJv1ABEBAAGJAjwEGAEKACYWIQRYSq8FbhoSeMR4RQCNjOhDW3SC +AAUCYzWllwIbDAUJCWYBgAAKCRCNjOhDW3SCAAEDEADIJn1yuKkg5E2qy2T28bWP +VJ8DwpmmKntfRIasb7nBcc4iKQGvbYF9i6OV6LMxExeiq4W1vj87/WMsmCxBWf7U ++KVUEaelARBBq9y0vgmL/El35VRCZRSsNXgnQbVK7ZFHC45GFZTRpMQPcx6ejPZt +demrRTzFNehF1SQ80Fn3U0n0f7sOmuFJ7lF4ahi5pfsgyLSNCIWlOMnxnnzYD4lT +xfwpoPKWhPoHcvgacspsYxm1eunAG0ElFCn4fLrlEBT3jyJO9PJ/d+mA2RRm6H39 +E8xRYzx+1Rupq1KKkvqU/S+he/NFKvitc7mWhoQ4OA5n5F5ENq9Cf4g57ePGouIM +Cu4u7K9VMzFW6EfnzMW5a8fPfIJp7V5fxE0r3/TBXuTCQEba0tGF4wxxgtB5pqx5 +jSMf9Vf9RoJW/HNaYzStTJNUcdPDoJxWqyVhbr3GCCDqV9OX6No+NJONr54wkLk4 +gmgX/lmai2A73SD9L8XF4D0KXOfIk+8t/Y2vQK+nystfkZX6pQ8fPasy4zIuZrJo +cQDB4tixns/QrPCr+Zf4PLVX1HlmQScvn+uaLUMAFTf6LozTYlHYW8y+HaVc6Qre ++Fbh0SKHcowXBN5PSq5yF3ElI6utDgrAwmNamVoPTQiA60jtKKY9hFM91KE2HGMs +2lEebIgAF9oRsC9BU9vSsg== +=f37X +-----END PGP PUBLIC KEY BLOCK----- diff --git a/hack/certs/localhost-key.pem b/hack/certs/localhost-key.pem new file mode 100644 index 0000000..a265878 --- /dev/null +++ b/hack/certs/localhost-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQC4ilhEy8jLTsSm +YW+hTenFrh8qvNYYYpo9PkQ/pscd4wyPi90BxKPoAIAWRsT5HE3cmJhD8cDtCly7 +9N4JOSH8rLoU1OSUQTtL4U84kQ9+rNrOexR9O+URBSLyu338KRdlpvUs25z7an/S +Hwf6CqMGx+pgqIp+6ucwjYXvTgWw56POimVf/u1Xs5xsy/saHhQUzQwlpVBGHOtB +wFVigQzUpCsefLzUGpVZc5J2FVRoRp+k76imHObZXQqMdAIW2DzIRJ6WUB4MHnkK +sO25EdLHlRdGS9r0Bf+CXqPJ6ePYLKXDRA3/KSLlnenShHp0NR2DQMhiG4oDi4Sb +1TQYFlXpAgMBAAECggEBAKPnPD7eQJlSfJbKM7uw19EbtdLfpchCy3tZsoRWPMPu +xVk5gDHx1SJaT2l5sbkPypgDcDnontHqQjMuaYcHl4g0YZHfBKYoyeG7XAGB1aFN +JYn/B1OzvuA/D6tHm747QOyoPVp6NBOZo62cohkTGXkMVr9C8r+HI4+cIzlIswVL +NW4+vCMZ9WQ5z98NpJX3isK6BDemWyfi702oTLG7XIwzn7GQWL0jQX+zOmYkmo+K +InGSnrHl7pILs2InDIA/3AMI4K/w/mqu11h2+VKKmHUyOKi47lBUdtpZp9KLMA6W +kJ3mwQX2RU8mAWpuGE+Bo23WApdcwRV+UYGtU+mE3YECgYEA0Q8OhvtF2/MWyLWp +Q0hNJ4PK7+Htnx/xzxzEOadLwTio19cO51J83zlAqQ6dCE4HUsQmZVlMl7kq6zfL +z7kn1x9H87YcBYnfxUFOQUKco5N/GibhJi85t0tjuReDU3GxZS7MsA7vsOn/uIdC +W91FS01QxgAl1bEnCK2gbNWNYlsCgYEA4fnxDoRuprj83Jz4MxbLSpqOrQE+S1V2 +3XGVHSo2x9i1d7T+CzVaug2r1Jqz02cRn15fE24B2KkoYz7eCNEJ7A0HfOLwwq2s +m9RMyAFY/RoktIZb09SwYneleuX3pvIQJ7hkX0oxg7EeVK/1H93lCrgBz4SVeUXe +nNzmkxY8FAsCgYEAt5MNIqJKudU/0IcUVqyKc4RbE0HEstION9v+wtGQx97FBKMn +xyC73hgcG1dltQEvlRIA1UYQ57oFYf7gzUq9HT2upObovERRZpjt6ohfm5PNLF2v +nyQg/j8JFmL7Qq63Iy5xNrgm6abQkmzTbG9khbcikntWvcqNiCVOlcMAH7kCgYEA +pXg02JGGyNSKbC0Q3bAiOkXEldBkQhuZx3tlWg7QQDRiZP6GS8TM45IhMbP6W6GM +WOtsqTiTZ4guR8YAJeqT3mKICh3PeG5eB1lEw+ugsu0S1ZHQ6eNDKUc9SCne10NH +Kx6teM1GRo1KjW6vCp+cGOY2hTMrlLrh0HE88ZWFdpMCgYEAigbuLOfVlSbIQ8vg +UJHf6JM5Lrc6oG6LW6fRUabnJibQ/TboNeV7PYd594swBrSwzJ/yRch8d6UIRVSB +cftPlut2YvHcoMlOhqS0oOOFNJx1JFNj8s0SMfQBd7MXG8r+OSL4pWPL7EhVmsS2 +WMXSd3FqGMORF7NKqrtXGb203mk= +-----END PRIVATE KEY----- diff --git a/hack/certs/localhost.pem b/hack/certs/localhost.pem new file mode 100644 index 0000000..e65a5f6 --- /dev/null +++ b/hack/certs/localhost.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEVzCCAr+gAwIBAgIRAIfOe7RuXJAXhuacBA29W9EwDQYJKoZIhvcNAQELBQAw +WTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMRcwFQYDVQQLDA51dGt1 +QHUtaG9tZS1wYzEeMBwGA1UEAwwVbWtjZXJ0IHV0a3VAdS1ob21lLXBjMB4XDTI0 +MDgxNjA4MTkwM1oXDTI2MTExNjA5MTkwM1owVjEnMCUGA1UEChMebWtjZXJ0IGRl +dmVsb3BtZW50IGNlcnRpZmljYXRlMSswKQYDVQQLDCJ1dGt1QHUtd29yay1zaWRl +cm8gKFV0a3Ugw5Z6ZGVtaXIpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAuIpYRMvIy07EpmFvoU3pxa4fKrzWGGKaPT5EP6bHHeMMj4vdAcSj6ACAFkbE ++RxN3JiYQ/HA7Qpcu/TeCTkh/Ky6FNTklEE7S+FPOJEPfqzaznsUfTvlEQUi8rt9 +/CkXZab1LNuc+2p/0h8H+gqjBsfqYKiKfurnMI2F704FsOejzoplX/7tV7OcbMv7 +Gh4UFM0MJaVQRhzrQcBVYoEM1KQrHny81BqVWXOSdhVUaEafpO+ophzm2V0KjHQC +Ftg8yESellAeDB55CrDtuRHSx5UXRkva9AX/gl6jyenj2Cylw0QN/yki5Z3p0oR6 +dDUdg0DIYhuKA4uEm9U0GBZV6QIDAQABo4GcMIGZMA4GA1UdDwEB/wQEAwIFoDAT +BgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBSUwQKJ6xTCiIbTU/iD3Wgy +OE1oFjBRBgNVHREESjBIgglsb2NhbGhvc3SCCyoubG9jYWxob3N0ghVteS1pbnN0 +YW5jZS5sb2NhbGhvc3SCFyoubXktaW5zdGFuY2UubG9jYWxob3N0MA0GCSqGSIb3 +DQEBCwUAA4IBgQAcK6EcBER1ExJ+jaLRLvyvkw0Z9JhB5AVVpgKuE9XJiJHrQCuJ +4nFeCZ2Nse8VkPtLdUCzup9ycbBFGzEp6XzjoeBpvC+uTJ6AhjNG/vPhSzgwX++a +69BzLGZVvrsh1BOwVy676gif99E5s19slzYHFm4kq/cXhkE8zMww0gFa3Xb9FGJv +hOfPA5yyJAC30bbUU0cKamGMRmS75jnWWlCrIwez+PEzEbLc7jELXfBtfX+zc8Ek +IIpK4S/IgbXNM0dXVNHdhGE56hRB/kBZSjHNy7QSB/mPStInM9YSDz4HKYXCLhQ4 +NNXZYst3YFhmgJ8cuVAyP0AZ8gNtbzLhwZXW00UE0q8t+xpgEQ87v4vIqXLCwqOK +iw+G5aGQpsZF6oHmPpPcQ5J7kHLIMvk+VVHVAivn4PRZqRkzbYxVennCcDqPStd4 +5Y9cKB4iWIHjfmdwGOGKJEfLlUHpG7R/8NoXJ8Ga9NU0UvgpTXg4yn7ChV/Cj4H6 +hCLKZcW/DPjnWiI= +-----END CERTIFICATE----- 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 <&1 & + +wait_for_bridge_ip() { + local timeout=60 + local interval=5 + + echo "Waiting for IP address $GATEWAY_IP to appear..." + + while ((timeout > 0)); do + if ip a | grep -q "$GATEWAY_IP"; then + echo "IP address $GATEWAY_IP found!" + return 0 # Success + fi + + ((timeout -= interval)) # Reduce the remaining time + sleep "$interval" + done + + echo "Timed out waiting for IP address $GATEWAY_IP." + return 1 # Failure +} + +# Wait for the bridge IP address to appear + +if ! wait_for_bridge_ip; then + exit 1 # Exit script on failure +fi + +# Bring Omni up + +OMNI_VERSION=${OMNI_VERSION:-latest} + +if [[ "${CI:-false}" == "true" ]]; then + REGISTRY_MIRROR_FLAGS=() + + for registry in docker.io k8s.gcr.io quay.io gcr.io ghcr.io registry.k8s.io factory.talos.dev; do + service="registry-${registry//./-}.ci.svc" + addr=$(python3 -c "import socket; print(socket.gethostbyname('${service}'))") + + REGISTRY_MIRROR_FLAGS+=("--registry-mirror=${registry}=http://${addr}:5000") + done +else + # use the value from the environment, if present + REGISTRY_MIRROR_FLAGS=("${REGISTRY_MIRROR_FLAGS:-}") +fi + +# Start Vault. + +docker run --rm -d --cap-add=IPC_LOCK -p 8200:8200 -e 'VAULT_DEV_ROOT_TOKEN_ID=dev-o-token' --name vault-dev hashicorp/vault:1.15 + +sleep 10 + +# Load key into Vault. + +docker cp hack/certs/key.private vault-dev:/tmp/key.private +docker exec -e VAULT_ADDR='http://0.0.0.0:8200' -e VAULT_TOKEN=dev-o-token vault-dev \ + vault kv put -mount=secret omni-private-key \ + private-key=@/tmp/key.private + +sleep 5 + +# Launch Omni in the background. + +export BASE_URL=https://localhost:8099/ +export AUTH_USERNAME="${AUTH0_TEST_USERNAME}" +export AUTH0_CLIENT_ID="${AUTH0_CLIENT_ID}" +export AUTH0_DOMAIN="${AUTH0_DOMAIN}" + +docker run -it -d --network host \ + -v ./hack/certs:/certs \ + -v "$(pwd)/${ARTIFACTS}/omni:/artifacts" \ + --cap-add=NET_ADMIN \ + --device=/dev/net/tun \ + -e SIDEROLINK_DEV_JOIN_TOKEN="${JOIN_TOKEN}" \ + -e VAULT_TOKEN=dev-o-token \ + -e VAULT_ADDR='http://127.0.0.1:8200' \ + "ghcr.io/siderolabs/omni:${OMNI_VERSION}" \ + omni-integration \ + --siderolink-wireguard-advertised-addr 10.11.0.1:50180 \ + --siderolink-api-advertised-url "grpc://10.11.0.1:8090" \ + --machine-api-bind-addr 0.0.0.0:8090 \ + --siderolink-wireguard-bind-addr 0.0.0.0:50180 \ + --event-sink-port 8091 \ + --auth-auth0-enabled true \ + --advertised-api-url "${BASE_URL}" \ + --auth-auth0-client-id "${AUTH0_CLIENT_ID}" \ + --auth-auth0-domain "${AUTH0_DOMAIN}" \ + --initial-users "${AUTH_USERNAME}" \ + --private-key-source "vault://secret/omni-private-key" \ + --public-key-files "/certs/key.public" \ + --bind-addr 0.0.0.0:8099 \ + --key /certs/localhost-key.pem \ + --cert /certs/localhost.pem \ + --etcd-embedded-unsafe-fsync=true \ + --create-initial-service-account \ + --initial-service-account-key-path=/artifacts/key \ + "${REGISTRY_MIRROR_FLAGS[@]}" \ + 2>&1 & + +# Determine the local IP +LOCAL_IP=$(ip -o route get to 8.8.8.8 | sed -n 's/.*src \([0-9.]\+\).*/\1/p') + +# Launch infra provider in the background +echo "Local IP: $LOCAL_IP" + +nice -n 10 ${ARTIFACTS}/provider \ + --insecure-skip-tls-verify \ + --api-advertise-address="$LOCAL_IP" \ + --use-local-boot-assets \ + --agent-test-mode \ + --api-power-mgmt-state-dir="$HOME/.talos/clusters/bare-metal" \ + --dhcp-proxy-iface-or-ip=$GATEWAY_IP \ + --debug \ + "$@" \ + 2>&1 & + +sleep 120 diff --git a/internal/provider/agent/controller.go b/internal/provider/agent/controller.go new file mode 100644 index 0000000..cd583f5 --- /dev/null +++ b/internal/provider/agent/controller.go @@ -0,0 +1,182 @@ +// 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 agent implements the metal agent controller. +package agent + +import ( + "context" + + "github.com/cosi-project/runtime/pkg/state" + "github.com/jhump/grpctunnel" + "github.com/jhump/grpctunnel/tunnelpb" + agentpb "github.com/siderolabs/talos-metal-agent/api/agent" + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + + "github.com/siderolabs/omni-infra-provider-bare-metal/api/specs" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/baremetal" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/stateutil" +) + +const machineIDMetadataKey = "machine-id" + +// Controller controls servers by establishing a reverse GRPC tunnel with them and by sending them commands. +type Controller struct { + logger *zap.Logger + grpcServer grpc.ServiceRegistrar + tunnelHandler *grpctunnel.TunnelServiceHandler + + wipeWithZeroes bool +} + +// NewController creates a new agent Controller. +func NewController(grpcServer grpc.ServiceRegistrar, state state.State, wipeWithZeroes bool, logger *zap.Logger) *Controller { + tunnelHandler := grpctunnel.NewTunnelServiceHandler( + grpctunnel.TunnelServiceHandlerOptions{ + OnReverseTunnelOpen: func(channel grpctunnel.TunnelChannel) { + handleTunnelEvent(channel, state, true, logger) + }, + OnReverseTunnelClose: func(channel grpctunnel.TunnelChannel) { + handleTunnelEvent(channel, state, false, logger) + }, + AffinityKey: func(channel grpctunnel.TunnelChannel) any { + id, ok := machineIDAffinityKey(channel.Context(), logger) + if !ok { + return "invalid" + } + + return id + }, + }, + ) + + tunnelpb.RegisterTunnelServiceServer(grpcServer, tunnelHandler.Service()) + + return &Controller{ + logger: logger, + grpcServer: grpcServer, + tunnelHandler: tunnelHandler, + wipeWithZeroes: wipeWithZeroes, + } +} + +func handleTunnelEvent(channel grpctunnel.TunnelChannel, state state.State, connected bool, logger *zap.Logger) { + affinityKey, ok := machineIDAffinityKey(channel.Context(), logger) + if !ok { + logger.Warn("invalid affinity key", zap.String("reason", "no machine ID in metadata")) + + return + } + + logger = logger.With(zap.String("machine_id", affinityKey), zap.Bool("connected", connected)) + + logger.Debug("machine tunnel event") + + if channel.Context().Err() != nil { // context is closed, probably the app is shutting down, nothing to do + return + } + + if connected { // if an agent is connected, update the boot mode to PXE + if _, err := stateutil.Modify(channel.Context(), state, baremetal.NewMachineStatus(affinityKey), func(status *baremetal.MachineStatus) error { + status.TypedSpec().Value.BootMode = specs.BootMode_BOOT_MODE_AGENT_PXE + + return nil + }); err != nil { + logger.Error("failed to update machine status", zap.String("machine_id", affinityKey), zap.Error(err)) + } + } +} + +// IsAccessible checks if the agent with the given ID is accessible. +func (c *Controller) IsAccessible(ctx context.Context, id string) (bool, error) { + channel := c.tunnelHandler.KeyAsChannel(id) + cli := agentpb.NewAgentServiceClient(channel) + + _, err := cli.Hello(ctx, &agentpb.HelloRequest{}) + if err != nil { + if status.Code(err) == codes.Unavailable { + return false, nil + } + + return false, err + } + + return true, nil +} + +// GetPowerManagement retrieves the IPMI information from the server with the given ID. +func (c *Controller) GetPowerManagement(ctx context.Context, id string) (*agentpb.GetPowerManagementResponse, error) { + channel := c.tunnelHandler.KeyAsChannel(id) + cli := agentpb.NewAgentServiceClient(channel) + + return cli.GetPowerManagement(ctx, &agentpb.GetPowerManagementRequest{}) +} + +// SetPowerManagement sets the IPMI information on the server with the given ID. +func (c *Controller) SetPowerManagement(ctx context.Context, id string, req *agentpb.SetPowerManagementRequest) error { + channel := c.tunnelHandler.KeyAsChannel(id) + cli := agentpb.NewAgentServiceClient(channel) + + _, err := cli.SetPowerManagement(ctx, req) + + return err +} + +// WipeDisks wipes the disks on the server with the given ID. +func (c *Controller) WipeDisks(ctx context.Context, id string) error { + channel := c.tunnelHandler.KeyAsChannel(id) + cli := agentpb.NewAgentServiceClient(channel) + + _, err := cli.WipeDisks(ctx, &agentpb.WipeDisksRequest{ + Zeroes: c.wipeWithZeroes, + }) + + return err +} + +// AllConnectedMachines returns a set of all connected machines. +func (c *Controller) AllConnectedMachines() map[string]struct{} { + allTunnels := c.tunnelHandler.AllReverseTunnels() + + machines := make(map[string]struct{}, len(allTunnels)) + + for _, tunnel := range allTunnels { + affinityKey, ok := machineIDAffinityKey(tunnel.Context(), c.logger) + if !ok { + c.logger.Warn("invalid affinity key", zap.String("reason", "no machine ID in metadata")) + + continue + } + + machines[affinityKey] = struct{}{} + } + + return machines +} + +func machineIDAffinityKey(ctx context.Context, logger *zap.Logger) (string, bool) { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + logger.Warn("invalid affinity key", zap.String("reason", "no metadata")) + + return "", false + } + + machineID := md.Get(machineIDMetadataKey) + if len(machineID) == 0 { + logger.Warn("invalid affinity key", zap.String("reason", "no machine ID in metadata")) + + return "", false + } + + if len(machineID) > 1 { + logger.Warn("multiple machine IDs in metadata", zap.Strings("machine_ids", machineID)) + } + + return machineID[0], true +} diff --git a/internal/provider/baremetal/baremetal.go b/internal/provider/baremetal/baremetal.go new file mode 100644 index 0000000..7477e8a --- /dev/null +++ b/internal/provider/baremetal/baremetal.go @@ -0,0 +1,15 @@ +// 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 baremetal contains bare-metal related resources. +package baremetal + +import ( + "github.com/siderolabs/omni/client/pkg/infra" + + providermeta "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/meta" +) + +// Namespace is the resource namespace of this provider. +var Namespace = infra.ResourceNamespace(providermeta.ProviderID) diff --git a/internal/provider/baremetal/machine_status.go b/internal/provider/baremetal/machine_status.go new file mode 100644 index 0000000..065a45d --- /dev/null +++ b/internal/provider/baremetal/machine_status.go @@ -0,0 +1,46 @@ +// 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 baremetal + +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/provider/meta" +) + +// NewMachineStatus creates a new MachineStatus. +func NewMachineStatus(id string) *MachineStatus { + return typed.NewResource[MachineStatusSpec, MachineStatusExtension]( + resource.NewMetadata(Namespace, MachineStatusType, id, resource.VersionUndefined), + protobuf.NewResourceSpec(&specs.MachineStatusSpec{}), + ) +} + +// MachineStatusType is the type of MachineStatus resource. +var MachineStatusType = infra.ResourceType("BareMetalMachineStatus", providermeta.ProviderID) + +// MachineStatus describes machineStatus configuration. +type MachineStatus = typed.Resource[MachineStatusSpec, MachineStatusExtension] + +// MachineStatusSpec wraps specs.MachineStatusSpec. +type MachineStatusSpec = protobuf.ResourceSpec[specs.MachineStatusSpec, *specs.MachineStatusSpec] + +// MachineStatusExtension providers auxiliary methods for MachineStatus resource. +type MachineStatusExtension struct{} + +// ResourceDefinition implements [typed.Extension] interface. +func (MachineStatusExtension) ResourceDefinition() meta.ResourceDefinitionSpec { + return meta.ResourceDefinitionSpec{ + Type: MachineStatusType, + Aliases: []resource.Type{}, + DefaultNamespace: Namespace, + PrintColumns: []meta.PrintColumn{}, + } +} diff --git a/internal/provider/boot/boot.go b/internal/provider/boot/boot.go new file mode 100644 index 0000000..3f9fd0a --- /dev/null +++ b/internal/provider/boot/boot.go @@ -0,0 +1,84 @@ +// 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 boot provides boot mode determination. +package boot + +import ( + "errors" + + "github.com/cosi-project/runtime/pkg/resource" + "github.com/siderolabs/omni/client/pkg/omni/resources/infra" + "go.uber.org/zap" + + "github.com/siderolabs/omni-infra-provider-bare-metal/api/specs" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/baremetal" +) + +// Mode describes the required boot mode. +type Mode struct { + PendingWipeID string + BootMode specs.BootMode + Installed bool + RequiresPowerMgmtConfig bool +} + +// DetermineRequiredMode determines the required boot mode. +func DetermineRequiredMode(infraMachine *infra.Machine, status *baremetal.MachineStatus, installStatus *infra.MachineState, logger *zap.Logger) (Mode, error) { + if infraMachine == nil { + return Mode{}, errors.New("infra machine is nil") + } + + if status == nil { + return Mode{}, errors.New("machine status is nil") + } + + tearingDown := infraMachine.Metadata().Phase() == resource.PhaseTearingDown + accepted := infraMachine.TypedSpec().Value.Accepted + requiredPowerMgmtConfig := status.TypedSpec().Value.PowerManagement == nil + installed := installStatus != nil && installStatus.TypedSpec().Value.Installed + allocated := infraMachine.TypedSpec().Value.ClusterTalosVersion != "" + pendingWipeID := "" + + wipeID := infraMachine.TypedSpec().Value.WipeId + if wipeID == "" { + wipeID = "initial" + } + + lastWipeID := status.TypedSpec().Value.LastWipeId + + if wipeID != lastWipeID { + pendingWipeID = wipeID + } + + bootIntoAgentMode := tearingDown || !accepted || !allocated || requiredPowerMgmtConfig || pendingWipeID != "" + + var requiredBootMode specs.BootMode + + switch { + case bootIntoAgentMode: + requiredBootMode = specs.BootMode_BOOT_MODE_AGENT_PXE + case installed: + requiredBootMode = specs.BootMode_BOOT_MODE_TALOS_DISK + default: + requiredBootMode = specs.BootMode_BOOT_MODE_TALOS_PXE + } + + logger.With( + zap.Bool("tearing_down", tearingDown), + zap.Bool("accepted", accepted), + zap.Bool("required_power_mgmt_config", requiredPowerMgmtConfig), + zap.Bool("installed", installed), + zap.String("wipe_id", wipeID), + zap.String("last_wipe_id", lastWipeID), + zap.Stringer("required_boot_mode", requiredBootMode), + ).Debug("determined boot mode") + + return Mode{ + PendingWipeID: pendingWipeID, + BootMode: requiredBootMode, + Installed: installed, + RequiresPowerMgmtConfig: requiredPowerMgmtConfig, + }, nil +} diff --git a/internal/provider/config/config.go b/internal/provider/config/config.go new file mode 100644 index 0000000..b41eb5d --- /dev/null +++ b/internal/provider/config/config.go @@ -0,0 +1,86 @@ +// 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 config serves machine configuration to the machines that request it via talos.config kernel argument. +package config + +import ( + "context" + "fmt" + "net/http" + "strings" + "text/template" + + "go.uber.org/zap" +) + +const machineConfigTemplate = `apiVersion: v1alpha1 +kind: SideroLinkConfig +apiUrl: {{ .APIURL }} +--- +apiVersion: v1alpha1 +kind: EventSinkConfig +endpoint: "[fdae:41e4:649b:9303::1]:8090" +--- +apiVersion: v1alpha1 +kind: KmsgLogConfig +name: omni-kmsg +url: "tcp://[fdae:41e4:649b:9303::1]:8092" +` + +// OmniClient is the interface to interact with Omni. +type OmniClient interface { + GetSiderolinkAPIURL(ctx context.Context) (string, error) +} + +// Handler handles machine configuration requests. +type Handler struct { + logger *zap.Logger + machineConfig string +} + +// NewHandler creates a new Handler. +func NewHandler(ctx context.Context, omniClient OmniClient, logger *zap.Logger) (*Handler, error) { + siderolinkAPIURL, err := omniClient.GetSiderolinkAPIURL(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get siderolink API URL: %w", err) + } + + tmpl, err := template.New("machine-config").Parse(machineConfigTemplate) + if err != nil { + return nil, err + } + + var sb strings.Builder + + if err = tmpl.Execute(&sb, struct { + APIURL string + }{ + APIURL: siderolinkAPIURL, + }); err != nil { + return nil, fmt.Errorf("failed to execute template: %w", err) + } + + return &Handler{ + machineConfig: sb.String(), + logger: logger, + }, nil +} + +// ServeHTTP serves the machine configuration. +// +// URL pattern: http://ip-of-this-provider:50042/config?&u=${uuid} +// +// Implements http.Handler interface. +func (s *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + uuid := req.URL.Query().Get("u") + + s.logger.Info("handle config request", zap.String("uuid", uuid)) + + w.WriteHeader(http.StatusOK) + + if _, err := w.Write([]byte(s.machineConfig)); err != nil { + s.logger.Error("failed to write response", zap.Error(err)) + } +} diff --git a/internal/provider/constants/constants.go b/internal/provider/constants/constants.go new file mode 100644 index 0000000..6643701 --- /dev/null +++ b/internal/provider/constants/constants.go @@ -0,0 +1,14 @@ +// 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 constants provides constants for the provider package. +package constants + +const ( + // IPXEPath is the path to the iPXE binaries. + IPXEPath = "/var/lib/ipxe" + + // TFTPPath is the path from which the TFTP server serves files. + TFTPPath = "/var/lib/tftp" +) diff --git a/internal/provider/controllers/controllers.go b/internal/provider/controllers/controllers.go new file mode 100644 index 0000000..af70212 --- /dev/null +++ b/internal/provider/controllers/controllers.go @@ -0,0 +1,6 @@ +// 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 controllers implements COSI controllers for the bare metal provider. +package controllers diff --git a/internal/provider/controllers/infra_machine_status.go b/internal/provider/controllers/infra_machine_status.go new file mode 100644 index 0000000..210c806 --- /dev/null +++ b/internal/provider/controllers/infra_machine_status.go @@ -0,0 +1,396 @@ +// 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 controllers + +import ( + "context" + "crypto/rand" + "fmt" + "math/big" + "time" + + "github.com/cosi-project/runtime/pkg/controller" + "github.com/cosi-project/runtime/pkg/controller/generic/qtransform" + "github.com/cosi-project/runtime/pkg/resource" + "github.com/cosi-project/runtime/pkg/safe" + "github.com/cosi-project/runtime/pkg/state" + "github.com/siderolabs/gen/xerrors" + omnispecs "github.com/siderolabs/omni/client/api/omni/specs" + "github.com/siderolabs/omni/client/pkg/omni/resources/infra" + agentpb "github.com/siderolabs/talos-metal-agent/api/agent" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + grpcstatus "google.golang.org/grpc/status" + + "github.com/siderolabs/omni-infra-provider-bare-metal/api/specs" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/baremetal" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/boot" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/meta" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/power" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/stateutil" +) + +const ( + ipmiUsername = "talos-agent" +) + +// AgentController is the interface for interacting with the Talos agent over the reverse GRPC tunnel. +type AgentController interface { + GetPowerManagement(ctx context.Context, id string) (*agentpb.GetPowerManagementResponse, error) + SetPowerManagement(ctx context.Context, id string, req *agentpb.SetPowerManagementRequest) error + WipeDisks(ctx context.Context, id string) error +} + +// APIPowerManager is the interface for reading power management information from the API state directory. +type APIPowerManager interface { + ReadManagementAddress(id resource.ID, logger *zap.Logger) (string, error) +} + +// InfraMachineController manages InfraMachine resource lifecycle. +type InfraMachineController = qtransform.QController[*infra.Machine, *infra.MachineStatus] + +// NewInfraMachineController initializes InfraMachineController. +func NewInfraMachineController(agentController AgentController, apiPowerManager APIPowerManager, state state.State, requeueInterval time.Duration) *InfraMachineController { + helper := &infraMachineControllerHelper{ + agentController: agentController, + apiPowerManager: apiPowerManager, + state: state, + requeueInterval: requeueInterval, + } + + return qtransform.NewQController( + qtransform.Settings[*infra.Machine, *infra.MachineStatus]{ + Name: meta.ProviderID + ".InfraMachineController", + MapMetadataFunc: func(infraMachine *infra.Machine) *infra.MachineStatus { + return infra.NewMachineStatus(infraMachine.Metadata().ID()) + }, + UnmapMetadataFunc: func(infraMachineStatus *infra.MachineStatus) *infra.Machine { + return infra.NewMachine(infraMachineStatus.Metadata().ID()) + }, + TransformFunc: helper.transform, + FinalizerRemovalFunc: helper.finalizerRemoval, + }, + qtransform.WithExtraMappedInput( + func(_ context.Context, _ *zap.Logger, _ controller.QRuntime, status *baremetal.MachineStatus) ([]resource.Pointer, error) { + ptr := infra.NewMachine(status.Metadata().ID()).Metadata() + + return []resource.Pointer{ptr}, nil + }, + ), + qtransform.WithExtraMappedInput( + qtransform.MapperSameID[*infra.MachineState, *infra.Machine](), + ), + ) +} + +type infraMachineControllerHelper struct { + agentController AgentController + apiPowerManager APIPowerManager + state state.State + requeueInterval time.Duration +} + +func (h *infraMachineControllerHelper) transform(ctx context.Context, reader controller.Reader, logger *zap.Logger, + infraMachine *infra.Machine, infraMachineStatus *infra.MachineStatus, +) error { + preferredPowerState := infraMachine.TypedSpec().Value.PreferredPowerState + accepted := infraMachine.TypedSpec().Value.Accepted + talosVersion := infraMachine.TypedSpec().Value.ClusterTalosVersion + extensions := infraMachine.TypedSpec().Value.Extensions + + logger = logger.With( + zap.Stringer("preferred_power_state", preferredPowerState), + zap.Bool("accepted", accepted), + zap.String("cluster_talos_version", talosVersion), + zap.String("wipe_id", infraMachine.TypedSpec().Value.WipeId), + zap.Strings("extensions", extensions), + ) + + logger.Info("transform infra machine") + + status, err := safe.ReaderGetByID[*baremetal.MachineStatus](ctx, reader, infraMachine.Metadata().ID()) + if err != nil { + if state.IsNotFoundError(err) { + return controller.NewRequeueErrorf(h.requeueInterval, "baremetal.MachineStatus not available yet, requeue") + } + + return err + } + + powerState := status.TypedSpec().Value.PowerState + bootMode := status.TypedSpec().Value.BootMode + lastWipeID := status.TypedSpec().Value.LastWipeId + + logger = logger.With( + zap.Stringer("power_state", powerState), + zap.Stringer("boot_mode", bootMode), + zap.String("last_wipe_id", lastWipeID), + ) + + if err = h.populateInfraMachineStatus(status, infraMachineStatus); err != nil { + return err + } + + installStatus, err := safe.ReaderGetByID[*infra.MachineState](ctx, reader, infraMachine.Metadata().ID()) + if err != nil && !state.IsNotFoundError(err) { + return err + } + + mode, err := boot.DetermineRequiredMode(infraMachine, status, installStatus, logger) + if err != nil { + return err + } + + requiredBootMode := mode.BootMode + + logger = logger.With( + zap.Stringer("required_boot_mode", requiredBootMode), + zap.Bool("requires_power_mgmt_config", mode.RequiresPowerMgmtConfig), + zap.String("pending_wipe_id", mode.PendingWipeID), + zap.Bool("installed", mode.Installed), + ) + + // The machine requires a reboot only if it is not in the desired mode, and either desired or the actual mode is agent mode. + // Switching from PXE booted Talos to booting from disk does not require a reboot by the provider, + // as Omni itself will do the switch. + requiresReboot := bootMode != requiredBootMode && (bootMode == specs.BootMode_BOOT_MODE_AGENT_PXE || requiredBootMode == specs.BootMode_BOOT_MODE_AGENT_PXE) + + if requiresReboot { + logger.Info("reboot to switch boot mode") + + return h.ensureReboot(ctx, status, logger) + } + + if mode.RequiresPowerMgmtConfig { + if err = h.ensurePowerManagement(ctx, status, logger); err != nil { + return err + } + + // the changes will trigger a new reconciliation, we can simply return here + return nil + } + + if mode.PendingWipeID != "" { + if err = h.wipe(ctx, infraMachine.Metadata().ID(), mode.PendingWipeID, logger); err != nil { + return err + } + + // the changes will trigger a new reconciliation, we can simply return here + return nil + } + + if !mode.Installed { // mark it as ready to use if there is no installation + infraMachineStatus.TypedSpec().Value.ReadyToUse = true + } + + return nil +} + +func (h *infraMachineControllerHelper) finalizerRemoval(ctx context.Context, reader controller.Reader, logger *zap.Logger, infraMachine *infra.Machine) (retErr error) { + if err := h.finalize(ctx, reader, infraMachine, logger); err != nil { + if xerrors.TypeIs[*controller.RequeueError](err) { // if the returned error is a requeue, respect it + return err + } + + logger.Warn("failed to finalize machine, but remove finalizer anyway", zap.Error(err)) + } + + return nil +} + +func (h *infraMachineControllerHelper) finalize(ctx context.Context, reader controller.Reader, infraMachine *infra.Machine, logger *zap.Logger) error { + status, err := safe.ReaderGetByID[*baremetal.MachineStatus](ctx, reader, infraMachine.Metadata().ID()) + if err != nil { + if state.IsNotFoundError(err) { + return nil + } + + return err + } + + bootMode := status.TypedSpec().Value.BootMode + + if bootMode != specs.BootMode_BOOT_MODE_AGENT_PXE { + return h.ensureReboot(ctx, status, logger) + } + + // the machine needs to be wiped, wipe it + if err = h.wipe(ctx, infraMachine.Metadata().ID(), "", logger); err != nil { + return fmt.Errorf("failed to wipe machine: %w", err) + } + + return nil +} + +func (h *infraMachineControllerHelper) populateInfraMachineStatus(status *baremetal.MachineStatus, infraMachineStatus *infra.MachineStatus) error { + infraMachineStatus.TypedSpec().Value.ReadyToUse = false + + // update power state + switch status.TypedSpec().Value.PowerState { + case specs.PowerState_POWER_STATE_ON: + infraMachineStatus.TypedSpec().Value.PowerState = omnispecs.InfraMachineStatusSpec_POWER_STATE_ON + case specs.PowerState_POWER_STATE_OFF: + infraMachineStatus.TypedSpec().Value.PowerState = omnispecs.InfraMachineStatusSpec_POWER_STATE_OFF + case specs.PowerState_POWER_STATE_UNKNOWN: + infraMachineStatus.TypedSpec().Value.PowerState = omnispecs.InfraMachineStatusSpec_POWER_STATE_UNKNOWN + default: + return fmt.Errorf("unknown power state %q", status.TypedSpec().Value.PowerState) + } + + return nil +} + +func (h *infraMachineControllerHelper) wipe(ctx context.Context, id resource.ID, pendingWipeID string, logger *zap.Logger) error { + if err := h.agentController.WipeDisks(ctx, id); err != nil { + statusCode := grpcstatus.Code(err) + if statusCode == codes.Unavailable { + return controller.NewRequeueErrorf(h.requeueInterval, "machine is not yet available, requeue wipe") + } + } + + // set the last wipe ID to the pending wipe ID, so the machine is marked as "clean" + if _, err := stateutil.Modify(ctx, h.state, baremetal.NewMachineStatus(id), func(res *baremetal.MachineStatus) error { + res.TypedSpec().Value.LastWipeId = pendingWipeID + + return nil + }); err != nil { + return err + } + + // mark as not installed + if _, err := safe.StateUpdateWithConflicts(ctx, h.state, infra.NewMachineState(id).Metadata(), func(res *infra.MachineState) error { + res.TypedSpec().Value.Installed = false + + return nil + }); err != nil { + return err + } + + logger.Info("wiped the machine and marked it as clean") + + return nil +} + +// ensureReboot makes sure that the machine is rebooted if it can be rebooted. +func (h *infraMachineControllerHelper) ensureReboot(ctx context.Context, status *baremetal.MachineStatus, logger *zap.Logger) error { + var powerClient power.Client + + powerClient, err := power.GetClient(status.TypedSpec().Value.PowerManagement) + if err != nil { + return err + } + + if err = powerClient.Reboot(ctx); err != nil { + return err + } + + logger.Info("rebooted machine, requeue") + + return controller.NewRequeueInterval(h.requeueInterval) +} + +// ensurePowerManagement makes sure that the power management for the machine is initialized if it hasn't been done yet. +func (h *infraMachineControllerHelper) ensurePowerManagement(ctx context.Context, status *baremetal.MachineStatus, logger *zap.Logger) error { + logger.Info("initializing power management") + + id := status.Metadata().ID() + + powerManagement, err := h.agentController.GetPowerManagement(ctx, id) + if err != nil { + if grpcstatus.Code(err) == codes.Unavailable { + return controller.NewRequeueErrorf(h.requeueInterval, "machine is not yet available, requeue getting power management") + } + + return err + } + + ipmiPassword, err := h.ensurePowerManagementOnAgent(ctx, id, powerManagement) + if err != nil { + return err + } + + _, err = stateutil.Modify(ctx, h.state, baremetal.NewMachineStatus(id), func(status *baremetal.MachineStatus) error { + status.TypedSpec().Value.PowerManagement = &specs.PowerManagement{} + + if powerManagement.Api != nil { + address, addressErr := h.apiPowerManager.ReadManagementAddress(id, logger) + if addressErr != nil { + return addressErr + } + + status.TypedSpec().Value.PowerManagement.Api = &specs.PowerManagement_API{ + Address: address, + } + } + + if powerManagement.Ipmi != nil { + status.TypedSpec().Value.PowerManagement.Ipmi = &specs.PowerManagement_IPMI{ + Username: ipmiUsername, + Password: ipmiPassword, + } + } + + return nil + }) + + return err +} + +// ensurePowerManagementOnAgent ensures that the power management (e.g., IPMI) is configured and credentials are set on the Talos machine running agent. +func (h *infraMachineControllerHelper) ensurePowerManagementOnAgent(ctx context.Context, id resource.ID, powerManagement *agentpb.GetPowerManagementResponse) (ipmiPassword string, err error) { + var ( + api *agentpb.SetPowerManagementRequest_API + ipmi *agentpb.SetPowerManagementRequest_IPMI + ) + + if powerManagement.Api != nil { + api = &agentpb.SetPowerManagementRequest_API{} + } + + if powerManagement.Ipmi != nil { + ipmiPassword, err = generateIPMIPassword() + if err != nil { + return "", err + } + + ipmi = &agentpb.SetPowerManagementRequest_IPMI{ + Username: ipmiUsername, + Password: ipmiPassword, + } + } + + if err = h.agentController.SetPowerManagement(ctx, id, &agentpb.SetPowerManagementRequest{ + Api: api, + Ipmi: ipmi, + }); err != nil { + if grpcstatus.Code(err) == codes.Unavailable { + return "", controller.NewRequeueErrorf(h.requeueInterval, "machine is not yet available, requeue setting power management") + } + + return "", err + } + + return ipmiPassword, nil +} + +var runes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + +// generateIPMIPassword returns a random password of length 16 for IPMI. +func generateIPMIPassword() (string, error) { + b := make([]rune, 16) + for i := range b { + rando, err := rand.Int( + rand.Reader, + big.NewInt(int64(len(runes))), + ) + if err != nil { + return "", err + } + + b[i] = runes[rando.Int64()] + } + + return string(b), 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/debug/debug.go b/internal/provider/debug/debug.go new file mode 100644 index 0000000..70fd22d --- /dev/null +++ b/internal/provider/debug/debug.go @@ -0,0 +1,6 @@ +// 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 debug provides a way to check if the build is a debug build. +package debug diff --git a/internal/provider/debug/disabled.go b/internal/provider/debug/disabled.go new file mode 100644 index 0000000..f8ef20f --- /dev/null +++ b/internal/provider/debug/disabled.go @@ -0,0 +1,10 @@ +// 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/. + +//go:build sidero.debug + +package debug + +// Enabled is set to true when the build is a debug build (WITH_DEBUG=true). +const Enabled = true diff --git a/internal/provider/debug/enabled.go b/internal/provider/debug/enabled.go new file mode 100644 index 0000000..aa26e16 --- /dev/null +++ b/internal/provider/debug/enabled.go @@ -0,0 +1,10 @@ +// 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/. + +//go:build !sidero.debug + +package debug + +// Enabled is set to true when the build is a debug build (WITH_DEBUG=true). +const Enabled = false diff --git a/internal/provider/dhcp/dhcp.go b/internal/provider/dhcp/dhcp.go new file mode 100644 index 0000000..2f5dcc3 --- /dev/null +++ b/internal/provider/dhcp/dhcp.go @@ -0,0 +1,6 @@ +// 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 dhcp implements DHCP proxy and other DHCP related functionality. +package dhcp diff --git a/internal/provider/dhcp/proxy.go b/internal/provider/dhcp/proxy.go new file mode 100644 index 0000000..7320813 --- /dev/null +++ b/internal/provider/dhcp/proxy.go @@ -0,0 +1,284 @@ +// 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 dhcp + +import ( + "context" + "errors" + "fmt" + "net" + "strconv" + + "github.com/insomniacslk/dhcp/dhcpv4" + "github.com/insomniacslk/dhcp/dhcpv4/server4" + "github.com/insomniacslk/dhcp/iana" + "github.com/siderolabs/gen/xslices" + "go.uber.org/zap" + "golang.org/x/sync/errgroup" +) + +// Proxy is a DHCP proxy server, adding PXE boot options to the DHCP responses. +type Proxy struct { + logger *zap.Logger + apiAdvertiseAddress string + proxyIfaceOrIP string + apiPort int +} + +// NewProxy creates a new DHCP proxy server. +func NewProxy(apiAdvertiseAddress string, apiPort int, proxyIfaceOrIP string, logger *zap.Logger) *Proxy { + return &Proxy{ + apiAdvertiseAddress: apiAdvertiseAddress, + apiPort: apiPort, + proxyIfaceOrIP: proxyIfaceOrIP, + logger: logger, + } +} + +// Run starts the DHCP proxy server. +func (p *Proxy) Run(ctx context.Context) error { + iface, err := p.determineInterface(p.proxyIfaceOrIP) + if err != nil { + return fmt.Errorf("failed to determine interface: %w", err) + } + + server, err := server4.NewServer(iface, nil, p.handlePacket()) + if err != nil { + return fmt.Errorf("failed to create DHCP server: %w", err) + } + + eg, ctx := errgroup.WithContext(ctx) + + eg.Go(func() error { + if err = server.Serve(); err != nil { + if errors.Is(err, net.ErrClosed) { + return nil + } + + return fmt.Errorf("failed to run DHCP server: %w", err) + } + + return nil + }) + + eg.Go(func() error { + <-ctx.Done() + + return server.Close() + }) + + return eg.Wait() +} + +func (p *Proxy) determineInterface(ifaceOrIP string) (string, error) { + interfaces, err := net.Interfaces() + if err != nil { + return "", fmt.Errorf("failed to get network interfaces: %w", err) + } + + targetIP := net.ParseIP(ifaceOrIP) + + for _, iface := range interfaces { + if iface.Name == ifaceOrIP { + return iface.Name, nil + } + + if targetIP == nil { // not an IP address, we are matching only against interfaces, skip + continue + } + + addrs, addrsErr := iface.Addrs() + if addrsErr != nil { + p.logger.Error("failed to list addresses for interface", zap.String("interface", iface.Name), zap.Error(addrsErr)) + } + + for _, addr := range addrs { + // Extract IP from address + var ip net.IP + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + + if ip != nil && ip.Equal(targetIP) { + return iface.Name, nil + } + } + } + + return "", fmt.Errorf("no interface found for: %s", ifaceOrIP) +} + +func (p *Proxy) handlePacket() func(conn net.PacketConn, peer net.Addr, m *dhcpv4.DHCPv4) { + return func(conn net.PacketConn, peer net.Addr, m *dhcpv4.DHCPv4) { + logger := p.logger.With(zap.String("source", m.ClientHWAddr.String())) + + if err := isBootDHCP(m); err != nil { + logger.Debug("ignoring packet", zap.Error(err)) + + return + } + + fwtype, err := validateDHCP(m) + if err != nil { + logger.Debug("invalid packet", zap.Error(err)) + + return + } + + resp, err := offerDHCP(m, p.apiAdvertiseAddress, p.apiPort, fwtype) + if err != nil { + logger.Error("failed to construct ProxyDHCP offer", zap.Error(err)) + + return + } + + logger.Info("offering boot response", zap.String("boot_filename", resp.BootFileNameOption())) + + _, err = conn.WriteTo(resp.ToBytes(), peer) + if err != nil { + logger.Error("failure sending response", zap.Error(err)) + } + } +} + +func isBootDHCP(pkt *dhcpv4.DHCPv4) error { + if pkt.MessageType() != dhcpv4.MessageTypeDiscover { + return fmt.Errorf("packet is %s, not %s", pkt.MessageType(), dhcpv4.MessageTypeDiscover) + } + + if pkt.Options[93] == nil { + return errors.New("not a PXE boot request (missing option 93)") + } + + return nil +} + +func validateDHCP(m *dhcpv4.DHCPv4) (fwtype Firmware, err error) { + arches := m.ClientArch() + + for _, arch := range arches { + switch arch { //nolint:exhaustive + case iana.INTEL_X86PC: + fwtype = FirmwareX86PC + case iana.EFI_IA32, iana.EFI_X86_64, iana.EFI_BC: + fwtype = FirmwareX86EFI + case iana.EFI_ARM64: + fwtype = FirmwareARMEFI + case iana.EFI_X86_HTTP, iana.EFI_X86_64_HTTP: + fwtype = FirmwareX86HTTP + case iana.EFI_ARM64_HTTP: + fwtype = FirmwareARMHTTP + } + } + + if fwtype == FirmwareUnsupported { + return 0, fmt.Errorf("unsupported client arch: %v", xslices.Map(arches, func(a iana.Arch) string { return a.String() })) + } + + // Now, identify special sub-breeds of client firmware based on + // the user-class option. Note these only change the "firmware + // type", not the architecture we're reporting to Booters. We need + // to identify these as part of making the internal chainloading + // logic work properly. + if userClasses := m.UserClass(); len(userClasses) > 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/provider/imagefactory/client.go b/internal/provider/imagefactory/client.go new file mode 100644 index 0000000..da37cce --- /dev/null +++ b/internal/provider/imagefactory/client.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 imagefactory + +import ( + "context" + "fmt" + "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" +) + +var agentModeExtensions = []string{ + // include all firmware extensions + "siderolabs/amd-ucode", + "siderolabs/amdgpu-firmware", + "siderolabs/bnx2-bnx2x", + "siderolabs/chelsio-firmware", + "siderolabs/i915-ucode", + "siderolabs/intel-ice-firmware", + "siderolabs/intel-ucode", + "siderolabs/qlogic-firmware", + "siderolabs/realtek-firmware", + // include the agent extension itself + "siderolabs/metal-agent", +} + +// Client is an image factory client. +type Client struct { + factoryClient *client.Client + pxeBaseURL string + agentModeTalosVersion string + machineLabelsMeta string +} + +// NewClient creates a new image factory client. +func NewClient(baseURL, pxeBaseURL, agentModeTalosVersion string, 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 + } + + return &Client{ + pxeBaseURL: pxeBaseURL, + agentModeTalosVersion: agentModeTalosVersion, + machineLabelsMeta: labelsMeta, + + 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 firmware extensions and the metal-agent extension. +func (c *Client) SchematicIPXEURL(ctx context.Context, agentMode bool, talosVersion, arch string, extensions, extraKernelArgs []string) (string, error) { + var metaValues []schematic.MetaValue + + if c.machineLabelsMeta != "" { + metaValues = append(metaValues, schematic.MetaValue{ + Key: meta.LabelsMeta, + Value: c.machineLabelsMeta, + }) + } + + if !agentMode && talosVersion == "" { + return "", fmt.Errorf("talosVersion is required when not booting into agent mode") + } + + if agentMode { + talosVersion = c.agentModeTalosVersion + + extensions = agentModeExtensions + } + + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + + sch := schematic.Schematic{ + Customization: schematic.Customization{ + ExtraKernelArgs: extraKernelArgs, + Meta: metaValues, + SystemExtensions: schematic.SystemExtensions{ + OfficialExtensions: extensions, + }, + }, + } + + schematicID, err := c.factoryClient.SchematicCreate(ctx, sch) + if err != nil { + return "", fmt.Errorf("failed to create schematic: %w", err) + } + + ipxeURL := fmt.Sprintf("%s/pxe/%s/%s/metal-%s", c.pxeBaseURL, schematicID, talosVersion, arch) + + 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/provider/imagefactory/imagefactory.go b/internal/provider/imagefactory/imagefactory.go new file mode 100644 index 0000000..7aa1a00 --- /dev/null +++ b/internal/provider/imagefactory/imagefactory.go @@ -0,0 +1,6 @@ +// 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 diff --git a/internal/provider/ip/ip.go b/internal/provider/ip/ip.go new file mode 100644 index 0000000..f3cfe65 --- /dev/null +++ b/internal/provider/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/provider/ipxe/boot.go b/internal/provider/ipxe/boot.go new file mode 100644 index 0000000..34410a4 --- /dev/null +++ b/internal/provider/ipxe/boot.go @@ -0,0 +1,35 @@ +// 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 "fmt" + +// BootFromDiskMethod defines a way to boot from disk. +type BootFromDiskMethod string + +const ( + // BootIPXEExit is a method to boot from disk using iPXE script with `exit` command. + BootIPXEExit BootFromDiskMethod = "ipxe-exit" + + // Boot404 is a method to boot from disk using HTTP 404 response to iPXE. + Boot404 BootFromDiskMethod = "http-404" + + // BootSANDisk is a method to boot from disk using iPXE script with `sanboot` command. + BootSANDisk BootFromDiskMethod = "ipxe-sanboot" +) + +// parseBootFromDiskMethod parses a boot from disk method. +func parseBootFromDiskMethod(method string) (BootFromDiskMethod, error) { + switch method { + case string(BootIPXEExit): + return BootIPXEExit, nil + case string(Boot404): + return Boot404, nil + case string(BootSANDisk): + return BootSANDisk, nil + default: + return "", fmt.Errorf("unknown boot from disk method: %s", method) + } +} diff --git a/internal/provider/ipxe/handler.go b/internal/provider/ipxe/handler.go new file mode 100644 index 0000000..364aadd --- /dev/null +++ b/internal/provider/ipxe/handler.go @@ -0,0 +1,352 @@ +// 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" + "net/http" + "os" + "slices" + "strconv" + "strings" + + "github.com/cosi-project/runtime/pkg/safe" + "github.com/cosi-project/runtime/pkg/state" + "github.com/siderolabs/omni/client/pkg/omni/resources/infra" + "github.com/siderolabs/talos-metal-agent/pkg/config" + "go.uber.org/zap" + + "github.com/siderolabs/omni-infra-provider-bare-metal/api/specs" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/baremetal" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/boot" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/stateutil" +) + +const ( + ipxeScriptTemplateFormat = `#!ipxe +chain --replace %s +` + + ipxeScriptTemplateFormatLocalAssets = `#!ipxe +kernel %s %s +initrd %s +boot +` + + // ipxeBootFromDiskExit script is used to skip PXE booting and boot from disk via exit. + ipxeBootFromDiskExit = `#!ipxe +exit +` + + // ipxeBootFromDiskSanboot script is used to skip PXE booting and boot from disk via sanboot. + ipxeBootFromDiskSanboot = `#!ipxe +sanboot --no-describe --drive 0x80 +` + + archAmd64 = "amd64" + archArm64 = "arm64" +) + +// 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, talosVersion, arch string, extensions, extraKernelArgs []string) (string, error) +} + +// HandlerOptions represents the options for the iPXE handler. +type HandlerOptions struct { + APIAdvertiseAddress string + BootFromDiskMethod string + APIPort int + UseLocalBootAssets bool + AgentTestMode bool +} + +// Handler represents an iPXE handler. +type Handler struct { + imageFactoryClient ImageFactoryClient + state state.State + + logger *zap.Logger + + bootFromDiskMethod BootFromDiskMethod + + defaultKernelArgs []string + agentKernelArgs []string + + options HandlerOptions +} + +// 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) { + ctx := req.Context() + query := req.URL.Query() + uuid := query.Get("uuid") + mac := query.Get("mac") + arch := query.Get("arch") + logger := handler.logger.With(zap.String("uuid", uuid), zap.String("mac", mac), zap.String("arch", arch)) + + logger.Info("handle iPXE request") + + decision, err := handler.makeBootDecision(ctx, arch, uuid, logger) + if err != nil { + handler.logger.Error("failed to check if Talos is installed", zap.Error(err)) + + w.WriteHeader(http.StatusInternalServerError) + + if _, err = w.Write([]byte("failed to check if Talos is installed")); err != nil { + handler.logger.Error("failed to write error response", zap.Error(err)) + } + + return + } + + w.WriteHeader(decision.statusCode) + + if _, err = w.Write([]byte(decision.body)); err != nil { + handler.logger.Error("failed to write ok response", zap.Error(err)) + + return + } + + // save the machine in any case + if _, err = stateutil.Modify(ctx, handler.state, baremetal.NewMachineStatus(uuid), func(status *baremetal.MachineStatus) error { + status.TypedSpec().Value.BootMode = decision.mode + + return nil + }); err != nil { + handler.logger.Error("failed to mark machine as dirty", zap.Error(err)) + } +} + +type bootDecision struct { + body string + statusCode int + mode specs.BootMode +} + +func (handler *Handler) makeBootDecision(ctx context.Context, arch, uuid string, logger *zap.Logger) (bootDecision, error) { + switch arch { // https://ipxe.org/cfg/buildarch + case archArm64: + arch = archArm64 + default: + arch = archAmd64 + } + + ress, err := handler.getResources(ctx, uuid) + if err != nil { + return bootDecision{statusCode: http.StatusInternalServerError}, fmt.Errorf("failed to get resources: %w", err) + } + + var requiredBootMode specs.BootMode + + if !ress.exists { // we see the machine for the first time, boot into agent mode + requiredBootMode = specs.BootMode_BOOT_MODE_AGENT_PXE + } else { + mode, modeErr := boot.DetermineRequiredMode(ress.infraMachine, ress.status, ress.installStatus, logger) + if modeErr != nil { + return bootDecision{statusCode: http.StatusInternalServerError}, fmt.Errorf("failed to determine required boot mode: %w", err) + } + + requiredBootMode = mode.BootMode + } + + logger = logger.With(zap.Stringer("required_boot_mode", requiredBootMode)) + + switch requiredBootMode { + case specs.BootMode_BOOT_MODE_AGENT_PXE: + body, statusCode, agentErr := handler.bootIntoAgentMode(ctx, arch) + if agentErr != nil { + return bootDecision{statusCode: http.StatusInternalServerError}, fmt.Errorf("failed to boot into agent mode: %w", agentErr) + } + + return bootDecision{ + mode: requiredBootMode, + body: body, + statusCode: statusCode, + }, nil + case specs.BootMode_BOOT_MODE_TALOS_PXE: + logger.Info("boot Talos over iPXE") + + consoleKernelArgs := handler.consoleKernelArgs(arch) + defaultExtraKernelArgs := slices.Concat(handler.defaultKernelArgs, consoleKernelArgs) + talosVersion := ress.infraMachine.TypedSpec().Value.ClusterTalosVersion + extensions := ress.infraMachine.TypedSpec().Value.Extensions + + var ipxeURL string + + ipxeURL, err = handler.imageFactoryClient.SchematicIPXEURL(ctx, false, talosVersion, arch, extensions, defaultExtraKernelArgs) + if err != nil { + return bootDecision{statusCode: http.StatusInternalServerError}, fmt.Errorf("failed to get schematic IPXE URL: %w", err) + } + + ipxeScript := fmt.Sprintf(ipxeScriptTemplateFormat, ipxeURL) + + return bootDecision{ + mode: requiredBootMode, + body: ipxeScript, + statusCode: http.StatusOK, + }, nil + case specs.BootMode_BOOT_MODE_TALOS_DISK: + logger.Info("boot from disk") + + switch handler.bootFromDiskMethod { + case Boot404: + return bootDecision{statusCode: http.StatusNotFound}, nil + case BootSANDisk: + return bootDecision{body: ipxeBootFromDiskSanboot, statusCode: http.StatusOK}, nil + case BootIPXEExit: + fallthrough + default: + return bootDecision{ + mode: requiredBootMode, + body: ipxeBootFromDiskExit, + statusCode: http.StatusOK, + }, nil + } + case specs.BootMode_BOOT_MODE_UNKNOWN: + fallthrough + default: + return bootDecision{statusCode: http.StatusInternalServerError}, fmt.Errorf("unknown boot mode: %s", requiredBootMode) + } +} + +type resources struct { + infraMachine *infra.Machine + status *baremetal.MachineStatus + installStatus *infra.MachineState + exists bool +} + +func (handler *Handler) getResources(ctx context.Context, id string) (resources, error) { + infraMachine, err := safe.StateGetByID[*infra.Machine](ctx, handler.state, id) + if err != nil { + if infraMachine == nil { + return resources{}, nil + } + + return resources{}, fmt.Errorf("failed to get infra machine: %w", err) + } + + status, err := safe.StateGetByID[*baremetal.MachineStatus](ctx, handler.state, id) + if err != nil { + return resources{}, fmt.Errorf("failed to get bare metal machine status: %w", err) + } + + installStatus, err := safe.StateGetByID[*infra.MachineState](ctx, handler.state, id) + if err != nil && !state.IsNotFoundError(err) { + return resources{}, fmt.Errorf("failed to get infra machine install status: %w", err) + } + + return resources{ + infraMachine: infraMachine, + status: status, + installStatus: installStatus, + exists: true, + }, nil +} + +func (handler *Handler) bootIntoAgentMode(ctx context.Context, arch string) (string, int, error) { + agentExtraKernelArgs := slices.Concat(handler.agentKernelArgs, handler.consoleKernelArgs(arch)) + + if handler.options.UseLocalBootAssets { + handler.logger.Info("boot agent mode using local assets") + + return handler.bootAgentModeViaLocalIPXEScript(arch, agentExtraKernelArgs) + } + + handler.logger.Info("boot agent mode using image factory") + + return handler.bootViaFactoryIPXEScript(ctx, true, arch, agentExtraKernelArgs) +} + +func (handler *Handler) bootViaFactoryIPXEScript(ctx context.Context, agentMode bool, arch string, kernelArgs []string) (string, int, error) { + ipxeURL, err := handler.imageFactoryClient.SchematicIPXEURL(ctx, agentMode, "", arch, nil, kernelArgs) + if err != nil { + return "", http.StatusInternalServerError, fmt.Errorf("failed to get schematic IPXE URL: %w", err) + } + + ipxeScript := fmt.Sprintf(ipxeScriptTemplateFormat, ipxeURL) + + return ipxeScript, http.StatusOK, nil +} + +func (handler *Handler) bootAgentModeViaLocalIPXEScript(arch string, kernelArgs []string) (string, int, error) { + hostPort := net.JoinHostPort(handler.options.APIAdvertiseAddress, strconv.Itoa(handler.options.APIPort)) + + kernel := fmt.Sprintf("http://%s/assets/kernel-%s", hostPort, arch) + initramfs := fmt.Sprintf("http://%s/assets/initramfs-metal-%s.xz", hostPort, arch) + + // read cmdline + cmdline, err := os.ReadFile(fmt.Sprintf("/assets/cmdline-metal-%s", arch)) + if err != nil { + return "", http.StatusInternalServerError, fmt.Errorf("failed to read cmdline: %w", err) + } + + cmdlineStr := strings.TrimSpace(string(cmdline)) + " " + strings.Join(kernelArgs, " ") + ipxeScript := fmt.Sprintf(ipxeScriptTemplateFormatLocalAssets, kernel, cmdlineStr, initramfs) + + return ipxeScript, http.StatusOK, nil +} + +func (handler *Handler) consoleKernelArgs(arch string) []string { + switch arch { + case archArm64: + return []string{"console=tty0", "console=ttyAMA0"} + default: + return []string{"console=tty0", "console=ttyS0"} + } +} + +// NewHandler creates a new iPXE server. +func NewHandler(imageFactoryClient ImageFactoryClient, state state.State, options HandlerOptions, logger *zap.Logger) (*Handler, error) { + bootFromDiskMethod, err := parseBootFromDiskMethod(options.BootFromDiskMethod) + if err != nil { + return nil, fmt.Errorf("failed to parse boot from disk method: %w", err) + } + + logger.Info("patch iPXE binaries") + + if err := patchBinaries(options.APIAdvertiseAddress, options.APIPort); err != nil { + return nil, err + } + + logger.Info("successfully patched iPXE binaries") + + apiHostPort := net.JoinHostPort(options.APIAdvertiseAddress, strconv.Itoa(options.APIPort)) + + talosConfigURL := fmt.Sprintf("http://%s/config?u=${uuid}", apiHostPort) + defaultKernelArgs := []string{ + "talos.config=" + talosConfigURL, + } + + agentExtraKernelArgs := []string{ + fmt.Sprintf("%s=%s", config.MetalProviderAddressKernelArg, apiHostPort), + } + + if options.AgentTestMode { + agentExtraKernelArgs = append(agentExtraKernelArgs, + fmt.Sprintf("%s=%s", config.TestModeKernelArg, "1"), + ) + } + + agentKernelArgs := slices.Concat(defaultKernelArgs, agentExtraKernelArgs) + + return &Handler{ + state: state, + imageFactoryClient: imageFactoryClient, + options: options, + defaultKernelArgs: defaultKernelArgs, + agentKernelArgs: agentKernelArgs, + bootFromDiskMethod: bootFromDiskMethod, + logger: logger, + }, nil +} diff --git a/internal/provider/ipxe/ipxe.go b/internal/provider/ipxe/ipxe.go new file mode 100644 index 0000000..583645a --- /dev/null +++ b/internal/provider/ipxe/ipxe.go @@ -0,0 +1,6 @@ +// 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 diff --git a/internal/provider/ipxe/patch.go b/internal/provider/ipxe/patch.go new file mode 100644 index 0000000..b3caccd --- /dev/null +++ b/internal/provider/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/provider/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(apiAdvertiseAddress string, apiPort int) error { + bootScript, err := buildBootScript(apiAdvertiseAddress, apiPort) + 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/provider/machinestatus/machinestatus.go b/internal/provider/machinestatus/machinestatus.go new file mode 100644 index 0000000..dd721f5 --- /dev/null +++ b/internal/provider/machinestatus/machinestatus.go @@ -0,0 +1,6 @@ +// 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 machinestatus provides functionality to poll the state of machines, i.e., power, connectivity, etc. +package machinestatus diff --git a/internal/provider/machinestatus/poller.go b/internal/provider/machinestatus/poller.go new file mode 100644 index 0000000..a78a8e4 --- /dev/null +++ b/internal/provider/machinestatus/poller.go @@ -0,0 +1,159 @@ +// 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 machinestatus + +import ( + "context" + "errors" + "time" + + "github.com/cosi-project/runtime/pkg/safe" + "github.com/cosi-project/runtime/pkg/state" + "go.uber.org/zap" + + "github.com/siderolabs/omni-infra-provider-bare-metal/api/specs" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/baremetal" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/power" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/stateutil" +) + +// AgentController is the interface for controlling Talos agent. +type AgentController interface { + AllConnectedMachines() map[string]struct{} + IsAccessible(ctx context.Context, machineID string) (bool, error) +} + +// Poller polls the machines periodically and updates their statuses. +type Poller struct { + agentController AgentController + state state.State + logger *zap.Logger +} + +// NewPoller creates a new Poller. +func NewPoller(agentController AgentController, state state.State, logger *zap.Logger) *Poller { + return &Poller{ + agentController: agentController, + state: state, + logger: logger, + } +} + +// Run starts the Poller. +func (m *Poller) Run(ctx context.Context) error { + ticker := time.NewTicker(time.Second * 30) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + if errors.Is(ctx.Err(), context.Canceled) { + return nil + } + + return ctx.Err() + case <-ticker.C: + } + + m.poll(ctx) + } +} + +func (m *Poller) poll(ctx context.Context) { + statusList, err := safe.StateListAll[*baremetal.MachineStatus](ctx, m.state) + if err != nil { + m.logger.Error("failed to get all machine statuses", zap.Error(err)) + + return + } + + connectedMachines := m.agentController.AllConnectedMachines() + machineIDSet := map[string]struct{}{} + + var numAgentConnected, numAgentDisconnected, numPoweredOn, numPoweredOff, numPowerUnknown int + + for status := range statusList.All() { + machineIDSet[status.Metadata().ID()] = struct{}{} + + powerState := m.getPowerState(ctx, status, m.logger) + + switch powerState { + case specs.PowerState_POWER_STATE_ON: + numPoweredOn++ + case specs.PowerState_POWER_STATE_OFF: + numPoweredOff++ + case specs.PowerState_POWER_STATE_UNKNOWN: + numPowerUnknown++ + } + + agentConnected, connectedErr := m.agentConnected(ctx, connectedMachines, status.Metadata().ID()) + if connectedErr != nil && !errors.Is(connectedErr, context.Canceled) { + m.logger.Error("failed to check connection", zap.String("machine_id", status.Metadata().ID()), zap.Error(connectedErr)) + + continue + } + + if agentConnected { + numAgentConnected++ + } else { + numAgentDisconnected++ + } + + if _, saveErr := stateutil.Modify(ctx, m.state, baremetal.NewMachineStatus(status.Metadata().ID()), func(status *baremetal.MachineStatus) error { + status.TypedSpec().Value.PowerState = powerState + + if agentConnected { // if the agent is connected, we know for sure that we are in the agent mode, so we update the status accordingly + status.TypedSpec().Value.BootMode = specs.BootMode_BOOT_MODE_AGENT_PXE + } + + return nil + }); saveErr != nil { + m.logger.Error("failed to save status", zap.String("machine_id", status.Metadata().ID()), zap.Error(saveErr)) + } + } + + m.logger.Info("polled machine statuses", zap.Int("agent_connected", numAgentConnected), zap.Int("agent_disconnected", numAgentDisconnected), + zap.Int("powered_on", numPoweredOn), zap.Int("powered_off", numPoweredOff), zap.Int("power_unknown", numPowerUnknown)) +} + +func (m *Poller) getPowerState(ctx context.Context, status *baremetal.MachineStatus, logger *zap.Logger) specs.PowerState { + powerClient, err := power.GetClient(status.TypedSpec().Value.PowerManagement) + if err != nil { + if errors.Is(err, power.ErrNoPowerManagementInfo) { + logger.Debug("no power management info yet, skip update", zap.String("machine_id", status.Metadata().ID())) + } else { + logger.Error("failed to get power client", zap.String("machine_id", status.Metadata().ID()), zap.Error(err)) + } + + return specs.PowerState_POWER_STATE_UNKNOWN + } + + poweredOn, err := powerClient.IsPoweredOn(ctx) + if err != nil { + logger.Error("failed to get power state", zap.String("machine_id", status.Metadata().ID()), zap.Error(err)) + + return specs.PowerState_POWER_STATE_UNKNOWN + } + + if poweredOn { + return specs.PowerState_POWER_STATE_ON + } + + return specs.PowerState_POWER_STATE_OFF +} + +func (m *Poller) agentConnected(ctx context.Context, connectedMachines map[string]struct{}, machineID string) (bool, error) { + if _, connected := connectedMachines[machineID]; !connected { // no tunnel connection, is definitely not connected + return false, nil + } + + // attempt to ping + accessible, err := m.agentController.IsAccessible(ctx, machineID) + if err != nil { + return false, err + } + + return accessible, nil +} diff --git a/internal/provider/meta/meta.go b/internal/provider/meta/meta.go new file mode 100644 index 0000000..c8bf8f0 --- /dev/null +++ b/internal/provider/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/provider/omni/omni.go b/internal/provider/omni/omni.go new file mode 100644 index 0000000..3fe332c --- /dev/null +++ b/internal/provider/omni/omni.go @@ -0,0 +1,91 @@ +// 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" + + "github.com/cosi-project/runtime/pkg/safe" + "github.com/cosi-project/runtime/pkg/state" + "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" + + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/meta" +) + +// Client is a wrapper around the Omni client. +type Client struct { + state state.State +} + +// BuildClient creates a new Omni client, wrapping the given Omni API client and state. +func BuildClient(state state.State) *Client { + return &Client{ + state: state, + } +} + +// GetSiderolinkAPIURL returns the SideroLink API URL. +func (c *Client) GetSiderolinkAPIURL(ctx context.Context) (string, error) { + connectionParams, err := safe.StateGetByID[*siderolink.ConnectionParams](ctx, c.state, 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) + + if err := c.state.Create(ctx, providerStatus); err != nil { + if !state.IsConflictError(err) { + return err + } + + if _, err = safe.StateUpdateWithConflicts(ctx, c.state, providerStatus.Metadata(), func(res *infra.ProviderStatus) error { + populate(res) + + return nil + }); err != nil { + return err + } + } + + return nil +} diff --git a/internal/provider/omni/tunnel/tunnel.go b/internal/provider/omni/tunnel/tunnel.go new file mode 100644 index 0000000..a210047 --- /dev/null +++ b/internal/provider/omni/tunnel/tunnel.go @@ -0,0 +1,67 @@ +// 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 tunnel provides the reverse GRPC tunnel to Omni. +package tunnel + +import ( + "context" + "errors" + "time" + + "github.com/cosi-project/runtime/pkg/state" + "github.com/jhump/grpctunnel" + "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/internal/provider/service" +) + +// Tunneler is the interface for the reverse GRPC tunnel. +type Tunneler interface { + Tunnel() *grpctunnel.ReverseTunnelServer +} + +// Tunnel represents the reverse GRPC tunnel to Omni. +type Tunnel struct { + tunneler Tunneler + logger *zap.Logger + state state.State +} + +// New creates a new Tunnel. +func New(state state.State, tunneler Tunneler, logger *zap.Logger) *Tunnel { + return &Tunnel{ + state: state, + tunneler: tunneler, + logger: logger, + } +} + +// Run starts the reverse GRPC tunnel to Omni. +func (t *Tunnel) Run(ctx context.Context) error { + reverseTunnelServer := t.tunneler.Tunnel() + providerServiceServer := service.NewProviderServiceServer(t.state, t.logger) + + providerpb.RegisterProviderServiceServer(reverseTunnelServer, providerServiceServer) + + // Open the reverse tunnel and serve requests. + for { + if _, err := reverseTunnelServer.Serve(ctx); err != nil { + if status.Code(err) == codes.Canceled || errors.Is(err, context.Canceled) { + return nil + } + + t.logger.Error("failed to serve reverse tunnel", zap.Error(err)) + + select { + case <-ctx.Done(): + return nil + case <-time.After(10 * time.Second): + } + } + } +} diff --git a/internal/provider/options.go b/internal/provider/options.go new file mode 100644 index 0000000..5ffe653 --- /dev/null +++ b/internal/provider/options.go @@ -0,0 +1,43 @@ +// 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 + +import "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/ipxe" + +// Options contains the provider options. +type Options struct { + Name string + Description string + OmniAPIEndpoint string + ImageFactoryBaseURL string + ImageFactoryPXEBaseURL string + AgentModeTalosVersion string + APIListenAddress string + APIAdvertiseAddress string + APIPowerMgmtStateDir string + DHCPProxyIfaceOrIP string + BootFromDiskMethod string + MachineLabels []string + APIPort int + + EnableResourceCache bool + AgentTestMode bool + InsecureSkipTLSVerify bool + UseLocalBootAssets bool + ClearState bool + WipeWithZeroes bool +} + +// DefaultOptions returns the default provider options. +var DefaultOptions = Options{ + Name: "Bare Metal", + Description: "Bare metal infrastructure provider", + OmniAPIEndpoint: "", + ImageFactoryBaseURL: "https://factory.talos.dev", + ImageFactoryPXEBaseURL: "https://pxe.factory.talos.dev", + AgentModeTalosVersion: "v1.9.0-alpha.2", + BootFromDiskMethod: string(ipxe.BootIPXEExit), + APIPort: 50042, +} diff --git a/internal/provider/power/api/api.go b/internal/provider/power/api/api.go new file mode 100644 index 0000000..131efd4 --- /dev/null +++ b/internal/provider/power/api/api.go @@ -0,0 +1,103 @@ +// 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 api provides power management functionality using an HTTP API, e.g., the HTTP API run by 'talosctl cluster create'. +package api + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "github.com/siderolabs/omni-infra-provider-bare-metal/api/specs" +) + +// Client is an API power management client: it communicates with an HTTP API to send power management commands. +type Client struct { + address string +} + +// Close implements the power.Client interface. +func (c *Client) Close() error { + return nil +} + +// Reboot implements the power.Client interface. +func (c *Client) Reboot(ctx context.Context) error { + return c.doPost(ctx, "/reboot") +} + +// PowerOff implements the power.Client interface. +func (c *Client) PowerOff(ctx context.Context) error { + return c.doPost(ctx, "/poweroff") +} + +func (c *Client) doPost(ctx context.Context, path string) error { + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + endpoint := "http://" + c.address + path + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, nil) + if err != nil { + return fmt.Errorf("failed to create request %q: %w", path, err) + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return fmt.Errorf("failed to make request %q: %w", path, err) + } + + defer closeBody(resp) + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected status code while resetting machine: %d", resp.StatusCode) + } + + return nil +} + +// IsPoweredOn implements the power.Client interface. +func (c *Client) IsPoweredOn(ctx context.Context) (bool, error) { + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + endpoint := "http://" + c.address + "/status" + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return false, err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return false, err + } + + defer closeBody(resp) + + var status struct { + PoweredOn bool + } + + if err = json.NewDecoder(resp.Body).Decode(&status); err != nil { //nolint:musttag + return false, err + } + + return status.PoweredOn, nil +} + +//nolint:errcheck +func closeBody(resp *http.Response) { + io.Copy(io.Discard, resp.Body) + resp.Body.Close() +} + +// NewClient creates a new API power management client. +func NewClient(info *specs.PowerManagement_API) (*Client, error) { + return &Client{address: info.Address}, nil +} diff --git a/internal/provider/power/api/manager.go b/internal/provider/power/api/manager.go new file mode 100644 index 0000000..e0eee3d --- /dev/null +++ b/internal/provider/power/api/manager.go @@ -0,0 +1,101 @@ +// 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 api + +import ( + "encoding/json" + "fmt" + "net" + "net/netip" + "os" + "path/filepath" + "strconv" + "strings" + + "go.uber.org/zap" +) + +// PowerManager is an API power management state manager. +type PowerManager struct { + stateDir string +} + +// NewPowerManager creates a new API PowerManager. +func NewPowerManager(stateDir string) *PowerManager { + return &PowerManager{stateDir: stateDir} +} + +// ReadManagementAddress reads the power management address from the state directory for the given machine ID. +func (manager *PowerManager) ReadManagementAddress(machineID string, logger *zap.Logger) (string, error) { + files, err := os.ReadDir(manager.stateDir) + if err != nil { + return "", fmt.Errorf("failed to read directory %s: %w", manager.stateDir, err) + } + + for _, file := range files { + if !strings.HasSuffix(file.Name(), ".config") { + continue + } + + configPath := filepath.Join(manager.stateDir, file.Name()) + + addr, addrErr := processConfigFile(configPath, machineID, logger) + if addrErr != nil { + logger.Warn("error processing config file", + zap.String("file", file.Name()), + zap.Error(addrErr)) + + continue + } + + if addr == "" { + continue + } + + return addr, nil + } + + return "", fmt.Errorf("no matching config file found for machine ID: %s", machineID) +} + +func processConfigFile(configPath, machineID string, logger *zap.Logger) (addr string, err error) { + configData, err := os.ReadFile(configPath) + if err != nil { + return "", fmt.Errorf("failed to read config file: %w", err) + } + + var conf launchConfig + if err = json.Unmarshal(configData, &conf); err != nil { + return "", fmt.Errorf("failed to unmarshal config file: %w", err) + } + + // Skip if NodeUUID doesn't match machineID + if conf.NodeUUID != machineID { + return "", nil + } + + if len(conf.GatewayAddrs) == 0 { + return "", fmt.Errorf("no gateway address found in matching machine launch config: %s", configPath) + } + + gatewayAddr := conf.GatewayAddrs[0].String() + + if len(conf.GatewayAddrs) > 1 { + logger.Warn("multiple gateway addresses found in machine launch config, using the first one", + zap.String("gateway_addr", gatewayAddr), + zap.String("file", configPath)) + } + + addr = net.JoinHostPort(gatewayAddr, strconv.Itoa(conf.APIPort)) + + return addr, nil +} + +// launchConfig is the JSON structure of the machine launch config, containing only the fields needed by this provisioner. +type launchConfig struct { + NodeUUID string + GatewayAddrs []netip.Addr + APIPort int +} diff --git a/internal/provider/power/ipmi/ipmi.go b/internal/provider/power/ipmi/ipmi.go new file mode 100644 index 0000000..05df65a --- /dev/null +++ b/internal/provider/power/ipmi/ipmi.go @@ -0,0 +1,72 @@ +// 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 ipmi provides power management functionality using IPMI. +package ipmi + +import ( + "context" + + goipmi "github.com/pensando/goipmi" + + "github.com/siderolabs/omni-infra-provider-bare-metal/api/specs" +) + +const ipmiUsername = "talos-agent" + +// Client is a wrapper around the goipmi client. +type Client struct { + ipmiClient *goipmi.Client +} + +// Close implements the power.Client interface. +func (c *Client) Close() error { + return c.ipmiClient.Close() +} + +// Reboot implements the power.Client interface. +func (c *Client) Reboot(context.Context) error { + return c.ipmiClient.Control(goipmi.ControlPowerCycle) +} + +// PowerOff implements the power.Client interface. +func (c *Client) PowerOff(context.Context) error { + return c.ipmiClient.Control(goipmi.ControlPowerDown) +} + +// IsPoweredOn implements the power.Client interface. +func (c *Client) IsPoweredOn(context.Context) (bool, error) { + req := &goipmi.Request{ + NetworkFunction: goipmi.NetworkFunctionChassis, + Command: goipmi.CommandChassisStatus, + Data: goipmi.ChassisStatusRequest{}, + } + + res := &goipmi.ChassisStatusResponse{} + + err := c.ipmiClient.Send(req, res) + if err != nil { + return false, err + } + + return res.IsSystemPowerOn(), nil +} + +// NewClient creates a new IPMI client. +func NewClient(info *specs.PowerManagement_IPMI) (*Client, error) { + conn := &goipmi.Connection{ + Hostname: info.Address, + Port: int(info.Port), + Username: ipmiUsername, + Password: info.Password, + Interface: "lanplus", + } + + client, err := goipmi.NewClient(conn) + if err != nil { + return nil, err + } + + return &Client{ipmiClient: client}, nil +} diff --git a/internal/provider/power/power.go b/internal/provider/power/power.go new file mode 100644 index 0000000..96d500a --- /dev/null +++ b/internal/provider/power/power.go @@ -0,0 +1,46 @@ +// 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 power provides power management functionality for machines. +package power + +import ( + "context" + "fmt" + "io" + + "github.com/siderolabs/omni-infra-provider-bare-metal/api/specs" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/power/api" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/power/ipmi" +) + +// ErrNoPowerManagementInfo is returned when there is no power management info present yet for a machine. +var ErrNoPowerManagementInfo = fmt.Errorf("no power management info found") + +// Client is the interface to interact with a single machine to send power commands to it. +type Client interface { + io.Closer + Reboot(ctx context.Context) error + IsPoweredOn(ctx context.Context) (bool, error) + PowerOff(ctx context.Context) error +} + +// GetClient returns a power management client for the given bare metal machine. +func GetClient(mgmt *specs.PowerManagement) (Client, error) { + if mgmt == nil || (mgmt.Api == nil && mgmt.Ipmi == nil) { + return nil, ErrNoPowerManagementInfo + } + + apiInfo := mgmt.Api + if apiInfo != nil { + return api.NewClient(apiInfo) + } + + ipmiInfo := mgmt.Ipmi + if ipmiInfo != nil { + return ipmi.NewClient(ipmiInfo) + } + + return nil, fmt.Errorf("no power management info found") +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go new file mode 100644 index 0000000..d40df96 --- /dev/null +++ b/internal/provider/provider.go @@ -0,0 +1,255 @@ +// 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" + "os" + "time" + + "github.com/cosi-project/runtime/pkg/controller/runtime" + runtimeoptions "github.com/cosi-project/runtime/pkg/controller/runtime/options" + "github.com/cosi-project/runtime/pkg/resource/protobuf" + "github.com/cosi-project/runtime/pkg/safe" + "github.com/cosi-project/runtime/pkg/state" + "github.com/hashicorp/go-multierror" + "github.com/siderolabs/omni/client/pkg/client" + "github.com/siderolabs/omni/client/pkg/omni/resources/infra" + "go.uber.org/zap" + "golang.org/x/sync/errgroup" + + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/agent" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/baremetal" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/config" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/controllers" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/dhcp" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/imagefactory" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/ip" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/ipxe" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/machinestatus" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/omni" + powerapi "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/power/api" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/server" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/tftp" +) + +//go:embed data/icon.svg +var icon []byte + +// 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 { + apiAdvertiseAddress, err := p.determineAPIAdvertiseAddress() + if err != nil { + return fmt.Errorf("failed to determine API advertise address: %w", err) + } + + p.logger.Info("starting provider", + zap.String("api_listen_address", p.options.APIListenAddress), + zap.String("api_advertise_address", apiAdvertiseAddress), + zap.Int("api_port", p.options.APIPort)) + + omniAPIClient, err := p.buildOmniAPIClient(p.options.OmniAPIEndpoint, p.options.InsecureSkipTLSVerify) + if err != nil { + return fmt.Errorf("failed to build omni client: %w", err) + } + + defer omniAPIClient.Close() //nolint:errcheck + + cosiRuntime, err := p.buildCOSIRuntime(omniAPIClient) + if err != nil { + return fmt.Errorf("failed to build COSI runtime: %w", err) + } + + omniState := state.WrapCore(cosiRuntime.CachedState()) + omniClient := omni.BuildClient(omniState) + + if p.options.ClearState { + if err = p.clearState(ctx, omniState); err != nil { + return fmt.Errorf("failed to clear state: %w", err) + } + + p.logger.Info("state cleared") + } + + 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.AgentModeTalosVersion, p.options.MachineLabels) + if err != nil { + return fmt.Errorf("failed to create image factory client: %w", err) + } + + ipxeHandler, err := ipxe.NewHandler(imageFactoryClient, omniState, ipxe.HandlerOptions{ + APIAdvertiseAddress: apiAdvertiseAddress, + APIPort: p.options.APIPort, + UseLocalBootAssets: p.options.UseLocalBootAssets, + AgentTestMode: p.options.AgentTestMode, + BootFromDiskMethod: p.options.BootFromDiskMethod, + }, 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, p.options.UseLocalBootAssets, configHandler, ipxeHandler, p.logger.With(zap.String("component", "server"))) + agentController := agent.NewController(srvr, omniState, p.options.WipeWithZeroes, p.logger.With(zap.String("component", "controller"))) //nolint:contextcheck // false positive + machineStatusPoller := machinestatus.NewPoller(agentController, omniState, p.logger.With(zap.String("component", "machine_status_poller"))) + dhcpProxy := dhcp.NewProxy(apiAdvertiseAddress, p.options.APIPort, p.options.DHCPProxyIfaceOrIP, p.logger.With(zap.String("component", "dhcp_proxy"))) + tftpServer := tftp.NewServer(p.logger.With(zap.String("component", "tftp_server"))) + apiPowerManager := powerapi.NewPowerManager(p.options.APIPowerMgmtStateDir) + + // todo: enable if we re-enable reverse tunnel on Omni: https://github.com/siderolabs/omni/pull/746 + // reverseTunnel := tunnel.New(omniState, omniAPIClient, p.logger.With(zap.String("component", "reverse_tunnel"))) + + if err = cosiRuntime.RegisterQController(controllers.NewInfraMachineController(agentController, apiPowerManager, omniState, 1*time.Minute)); err != nil { + return fmt.Errorf("failed to register controller: %w", err) + } + + return p.runComponents(ctx, map[string]func(context.Context) error{ + "COSI runtime": cosiRuntime.Run, + "machine status poller": machineStatusPoller.Run, + "server": srvr.Run, + // "reverse tunnel": reverseTunnel.Run, + "DHCP proxy": dhcpProxy.Run, + "TFTP server": tftpServer.Run, + }) +} + +func (p *Provider) buildCOSIRuntime(omniAPIClient *client.Client) (*runtime.Runtime, error) { + omniState := omniAPIClient.Omni().State() + + if err := protobuf.RegisterResource(baremetal.MachineStatusType, &baremetal.MachineStatus{}); err != nil { + return nil, fmt.Errorf("failed to register protobuf resource: %w", err) + } + + var options []runtimeoptions.Option + + if p.options.EnableResourceCache { + options = append(options, safe.WithResourceCache[*baremetal.MachineStatus]()) + options = append(options, safe.WithResourceCache[*infra.Machine]()) + options = append(options, safe.WithResourceCache[*infra.MachineStatus]()) + } + + cosiRuntime, err := runtime.NewRuntime(omniState, p.logger.With(zap.String("component", "cosi_runtime")), options...) + if err != nil { + return nil, fmt.Errorf("failed to create runtime: %w", err) + } + + return cosiRuntime, nil +} + +func (p *Provider) runComponents(ctx context.Context, components map[string]func(context.Context) error) error { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + eg, ctx := errgroup.WithContext(ctx) + + for name, f := range components { + eg.Go(func() error { + defer cancel() // cancel the parent context, so all other components are also stopped + + p.logger.Info("start component ", zap.String("name", name)) + + err := f(ctx) + 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 + }) + } + + if err := eg.Wait(); err != nil { + return fmt.Errorf("failed to run components: %w", err) + } + + return nil +} + +func (p *Provider) determineAPIAdvertiseAddress() (string, error) { + if p.options.APIAdvertiseAddress != "" { + return p.options.APIAdvertiseAddress, nil + } + + 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 API advertise address explicitly`, len(routableIPs), routableIPs) + } + + return routableIPs[0], nil +} + +// buildOmniAPIClient creates a new Omni API client. +func (p *Provider) buildOmniAPIClient(endpoint string, insecureSkipTLSVerify bool) (*client.Client, error) { + serviceAccountKey := os.Getenv("OMNI_SERVICE_ACCOUNT_KEY") + + cliOpts := []client.Option{ + client.WithInsecureSkipTLSVerify(insecureSkipTLSVerify), + } + + if serviceAccountKey != "" { + cliOpts = append(cliOpts, client.WithServiceAccount(serviceAccountKey)) + } + + return client.New(endpoint, cliOpts...) +} + +// clearState clears the persistent state of this provider. Useful for debugging purposes. +func (p *Provider) clearState(ctx context.Context, st state.State) error { + list, err := st.List(ctx, baremetal.NewMachineStatus("").Metadata()) + if err != nil { + return fmt.Errorf("failed to list bare metal machinees: %w", err) + } + + var errs error + + for _, item := range list.Items { + res, getErr := st.Get(ctx, item.Metadata()) + if getErr != nil { + errs = multierror.Append(errs, getErr) + + continue + } + + if destroyErr := st.Destroy(ctx, item.Metadata(), state.WithDestroyOwner(res.Metadata().Owner())); destroyErr != nil { + errs = multierror.Append(errs, destroyErr) + + continue + } + } + + return errs +} diff --git a/internal/provider/server/server.go b/internal/provider/server/server.go new file mode 100644 index 0000000..a6d7d07 --- /dev/null +++ b/internal/provider/server/server.go @@ -0,0 +1,150 @@ +// 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, listenAddress string, port int, serveAssetsDir bool, 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(listenAddress, strconv.Itoa(port)), + Handler: newMultiHandler(configHandler, ipxeHandler, grpcServer, serveAssetsDir, logger), + 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, serveAssetsDir bool, logger *zap.Logger) http.Handler { + mux := http.NewServeMux() + + mux.Handle("/config", configHandler) + mux.Handle("/ipxe", ipxeHandler) + + if serveAssetsDir { + mux.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("/assets/")))) + } + + loggingMiddleware := func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + start := time.Now() + + next.ServeHTTP(w, req) + + logger.Info("request", + zap.String("method", req.Method), + zap.String("path", req.URL.Path), + zap.Duration("duration", time.Since(start)), + ) + }) + } + + multi := &multiHandler{ + httpHandler: loggingMiddleware(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/provider/service/service.go b/internal/provider/service/service.go new file mode 100644 index 0000000..9599136 --- /dev/null +++ b/internal/provider/service/service.go @@ -0,0 +1,53 @@ +// 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" + + "github.com/cosi-project/runtime/pkg/safe" + "github.com/cosi-project/runtime/pkg/state" + "go.uber.org/zap" + + "github.com/siderolabs/omni-infra-provider-bare-metal/api/provider" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/baremetal" + "github.com/siderolabs/omni-infra-provider-bare-metal/internal/provider/power" +) + +// ProviderServiceServer is the bare metal infra provider service server. +type ProviderServiceServer struct { + providerpb.UnimplementedProviderServiceServer + + logger *zap.Logger + state state.State +} + +// NewProviderServiceServer creates a new ProviderServiceServer. +func NewProviderServiceServer(state state.State, logger *zap.Logger) *ProviderServiceServer { + return &ProviderServiceServer{ + state: state, + logger: logger, + } +} + +// RebootMachine reboots a machine. +func (p *ProviderServiceServer) RebootMachine(ctx context.Context, request *providerpb.RebootMachineRequest) (*providerpb.RebootMachineResponse, error) { + status, err := safe.StateGetByID[*baremetal.MachineStatus](ctx, p.state, request.Id) + if err != nil { + return nil, err + } + + powerClient, err := power.GetClient(status.TypedSpec().Value.PowerManagement) + if err != nil { + return nil, err + } + + if err = powerClient.Reboot(ctx); err != nil { + return nil, err + } + + return &providerpb.RebootMachineResponse{}, nil +} diff --git a/internal/provider/stateutil/stateutil.go b/internal/provider/stateutil/stateutil.go new file mode 100644 index 0000000..a8a848d --- /dev/null +++ b/internal/provider/stateutil/stateutil.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/. + +// Package stateutil implements helpers for working with state. +package stateutil + +import ( + "context" + "sync" + + "github.com/cosi-project/runtime/pkg/resource" + "github.com/cosi-project/runtime/pkg/safe" + "github.com/cosi-project/runtime/pkg/state" +) + +var mu sync.Mutex + +// Modify modifies a resource in the state. +func Modify[T resource.Resource](ctx context.Context, st state.State, res T, updateFn func(status T) error) (T, error) { + var zero T + + mu.Lock() + defer mu.Unlock() + + if _, err := safe.StateGet[T](ctx, st, res.Metadata()); err != nil { + if state.IsNotFoundError(err) { + if err = updateFn(res); err != nil { + return zero, err + } + + if err = st.Create(ctx, res); err != nil { + return zero, err + } + + return res, nil + } + } + + return safe.StateUpdateWithConflicts[T](ctx, st, res.Metadata(), updateFn) +} diff --git a/internal/provider/tftp/tftp_server.go b/internal/provider/tftp/tftp_server.go new file mode 100644 index 0000000..6b88ae6 --- /dev/null +++ b/internal/provider/tftp/tftp_server.go @@ -0,0 +1,126 @@ +// 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/provider/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 { + logger.Info("file requested", zap.String("filename", filename)) + + 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/qemu/machines.go b/internal/qemu/machines.go new file mode 100644 index 0000000..9606a47 --- /dev/null +++ b/internal/qemu/machines.go @@ -0,0 +1,297 @@ +// 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 qemu + +import ( + "context" + "errors" + "fmt" + "math/big" + "net/netip" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/google/uuid" + talosnet "github.com/siderolabs/net" + clientconfig "github.com/siderolabs/talos/pkg/machinery/client/config" + "github.com/siderolabs/talos/pkg/machinery/config/machine" + "github.com/siderolabs/talos/pkg/provision" + "github.com/siderolabs/talos/pkg/provision/providers" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zapio" +) + +const ( + providerName = "qemu" + diskDriver = "virtio" + + // TalosctlBinary is the default name of the talosctl binary. + TalosctlBinary = "talosctl" +) + +// Machines represents a set of Talos QEMU machines. +type Machines struct { + logger *zap.Logger + + subnetCIDR netip.Prefix + + stateDir string + cniDir string + + nameservers []netip.Addr + + options Options + + nanoCPUs int64 +} + +// New creates a new set of Talos QEMU machines. +func New(options Options, logger *zap.Logger) (*Machines, error) { + if logger == nil { + logger = zap.NewNop() + } + + talosDir, err := clientconfig.GetTalosDirectory() + if err != nil { + return nil, fmt.Errorf("failed to get Talos directory: %w", err) + } + + stateDir := filepath.Join(talosDir, "clusters") + cniDir := filepath.Join(talosDir, "cni") + + logger = logger.With(zap.String("cluster_name", options.Name), zap.String("state_dir", stateDir)) + + if options.TalosctlPath == "" { + if options.TalosctlPath, err = findTalosctl(); err != nil { + return nil, fmt.Errorf("failed to find talosctl binary: %w", err) + } + } + + cidr, err := netip.ParsePrefix(options.CIDR) + if err != nil { + return nil, fmt.Errorf("failed to parse subnet CIDR: %w", err) + } + + nss, err := parseNameservers(options.Nameservers) + if err != nil { + return nil, fmt.Errorf("failed to parse nameservers: %w", err) + } + + nanoCPUs, err := parseCPUShare(options.CPUs) + if err != nil { + return nil, fmt.Errorf("failed to parse CPU share: %w", err) + } + + return &Machines{ + options: options, + stateDir: stateDir, + cniDir: cniDir, + subnetCIDR: cidr, + nameservers: nss, + nanoCPUs: nanoCPUs, + logger: logger, + }, nil +} + +// Run starts the provisioner by provisioning a QEMU cluster with the given number of machines. If the cluster already exists, it is loaded instead. +func (machines *Machines) Run(ctx context.Context) error { + machines.logger.Info("provision a new set of machines", + zap.String("name", machines.options.Name), + zap.String("subnet_cidr", machines.subnetCIDR.String()), + zap.Int("num_machines", machines.options.NumMachines), + zap.Int64("mem_size", machines.options.MemSize), + zap.Uint64("disk_size", machines.options.DiskSize), + zap.Int64("nano_cpus", machines.nanoCPUs), + zap.Strings("nameservers", machines.options.Nameservers), + zap.Int("mtu", machines.options.MTU), + zap.String("talosctl_path", machines.options.TalosctlPath), + zap.String("cni_bundle_url", machines.options.CNIBundleURL), + ) + + qemuProvisioner, err := providers.Factory(ctx, providerName) + if err != nil { + return fmt.Errorf("failed to create provisioner: %w", err) + } + + gatewayAddr, err := talosnet.NthIPInNetwork(machines.subnetCIDR, 1) + if err != nil { + return fmt.Errorf("failed to get gateway address: %w", err) + } + + existingCluster, err := qemuProvisioner.Reflect(ctx, machines.options.Name, machines.stateDir) + if err == nil { + machines.logger.Info("loaded existing cluster") + + if len(existingCluster.Info().Nodes) != machines.options.NumMachines { + machines.logger.Warn("number of nodes in the existing cluster does not match the requested number of machines", + zap.Int("requested", machines.options.NumMachines), + zap.Int("existing", len(existingCluster.Info().Nodes))) + } + + return nil + } + + if !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("failed to load existing machines: %w", err) + } + + machines.logger.Info("create a new set of machines") + + nodes := make([]provision.NodeRequest, 0, machines.options.NumMachines) + + for i := range machines.options.NumMachines { + nodeUUID := uuid.New() + + machines.logger.Info("generated node UUID", zap.String("uuid", nodeUUID.String())) + + var ip netip.Addr + + ip, err = talosnet.NthIPInNetwork(machines.subnetCIDR, i+2) + if err != nil { + return fmt.Errorf("failed to calculate offset %d from CIDR %s: %w", i+2, machines.subnetCIDR, err) + } + + nodes = append(nodes, provision.NodeRequest{ + Name: nodeUUID.String(), + Type: machine.TypeWorker, + + IPs: []netip.Addr{ip}, + Memory: machines.options.MemSize, + NanoCPUs: machines.nanoCPUs, + Disks: []*provision.Disk{ + { + Size: machines.options.DiskSize, + Driver: diskDriver, + }, + }, + SkipInjectingConfig: true, + UUID: &nodeUUID, + PXEBooted: true, + DefaultBootOrder: "nc", // first network, then disk + }) + } + + request := provision.ClusterRequest{ + Name: machines.options.Name, + + Network: provision.NetworkRequest{ + Name: machines.options.Name, + CIDRs: []netip.Prefix{machines.subnetCIDR}, + GatewayAddrs: []netip.Addr{gatewayAddr}, + MTU: machines.options.MTU, + Nameservers: machines.nameservers, + CNI: provision.CNIConfig{ + BinPath: []string{filepath.Join(machines.cniDir, "bin")}, + ConfDir: filepath.Join(machines.cniDir, "conf.d"), + CacheDir: filepath.Join(machines.cniDir, "cache"), + BundleURL: machines.options.CNIBundleURL, + }, + }, + + SelfExecutable: machines.options.TalosctlPath, + StateDirectory: machines.stateDir, + + Nodes: nodes, + } + + logWriter := zapio.Writer{ + Log: machines.logger, + Level: zapcore.InfoLevel, + } + defer logWriter.Close() //nolint:errcheck + + if _, err = qemuProvisioner.Create(ctx, request, + provision.WithBootlader(true), + provision.WithUEFI(false), // UEFI doesn't work correctly on PXE timeout, as it drops to UEFI shell + provision.WithLogWriter(&logWriter), + ); err != nil { + return fmt.Errorf("failed to create machines: %w", err) + } + + <-ctx.Done() + + return nil +} + +// Destroy destroys the existing set of machines. +func (machines *Machines) Destroy(ctx context.Context) error { + qemuProvisioner, err := providers.Factory(ctx, providerName) + if err != nil { + return fmt.Errorf("failed to create provisioner: %w", err) + } + + // attempt to load existing cluster + cluster, err := qemuProvisioner.Reflect(ctx, machines.options.Name, machines.stateDir) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil + } + + return fmt.Errorf("failed to load existing cluster while clearing state: %w", err) + } + + logWriter := zapio.Writer{ + Log: machines.logger, + Level: zapcore.InfoLevel, + } + defer logWriter.Close() //nolint:errcheck + + if err = qemuProvisioner.Destroy(ctx, cluster, provision.WithDeleteOnErr(true), provision.WithLogWriter(&logWriter)); err != nil { + if strings.Contains(err.Error(), "no such network interface") { + return nil + } + + return fmt.Errorf("failed to destroy cluster: %w", err) + } + + return nil +} + +func parseCPUShare(cpus string) (int64, error) { + cpu, ok := new(big.Rat).SetString(cpus) + if !ok { + return 0, fmt.Errorf("failed to parse as a rational number: %s", cpus) + } + + nano := cpu.Mul(cpu, big.NewRat(1e9, 1)) + if !nano.IsInt() { + return 0, errors.New("value is too precise") + } + + return nano.Num().Int64(), nil +} + +func findTalosctl() (string, error) { + // check the current working directory + if stat, err := os.Stat(TalosctlBinary); err == nil && !stat.IsDir() { + return TalosctlBinary, nil + } + + // check the PATH + path, err := exec.LookPath(TalosctlBinary) + if err != nil { + return "", fmt.Errorf("failed to find talosctl binary: %w", err) + } + + return path, nil +} + +func parseNameservers(nameservers []string) ([]netip.Addr, error) { + nss := make([]netip.Addr, 0, len(nameservers)) + + for _, ns := range nameservers { + addr, err := netip.ParseAddr(ns) + if err != nil { + return nil, fmt.Errorf("failed to parse nameserver %q: %w", ns, err) + } + + nss = append(nss, addr) + } + + return nss, nil +} diff --git a/internal/qemu/options.go b/internal/qemu/options.go new file mode 100644 index 0000000..c934f7e --- /dev/null +++ b/internal/qemu/options.go @@ -0,0 +1,36 @@ +// 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 qemu + +// Options for the set of machines. +type Options struct { + Name string + CIDR string + CNIBundleURL string + TalosctlPath string + CPUs string + + Nameservers []string + + NumMachines int + MTU int + + DiskSize uint64 + MemSize int64 +} + +// DefaultOptions are the default options for the set of machines. +var DefaultOptions = Options{ + Name: "bare-metal", + CIDR: "172.42.0.0/24", + CNIBundleURL: "https://github.com/siderolabs/talos/releases/latest/download/talosctl-cni-bundle-amd64.tar.gz", + NumMachines: 4, + Nameservers: []string{"1.1.1.1", "1.0.0.1"}, + MTU: 1440, + + CPUs: "3", + DiskSize: 6 * 1024 * 1024 * 1024, + MemSize: 3072 * 1024 * 1024, +} diff --git a/internal/qemu/qemu.go b/internal/qemu/qemu.go new file mode 100644 index 0000000..b874a39 --- /dev/null +++ b/internal/qemu/qemu.go @@ -0,0 +1,6 @@ +// 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 qemu provides functionality to bring up Talos QEMU VMs to develop/test the provider. +package qemu 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" + }() +)