From 9ea9d161869303a97971c374f9969ea262eaabdf Mon Sep 17 00:00:00 2001 From: Utku Ozdemir Date: Thu, 11 Jul 2024 23:20:18 +0200 Subject: [PATCH] feat: add initial sources The initial implementation. Signed-off-by: Utku Ozdemir --- .codecov.yml | 2 +- .conform.yaml | 2 +- .dockerignore | 4 +- .github/workflows/ci.yaml | 2 +- .github/workflows/slack-notify.yaml | 2 +- .gitignore | 2 +- .golangci.yml | 2 +- .kres.yaml | 11 + .markdownlint.json | 2 +- Dockerfile | 97 +++- Makefile | 5 +- api/specs/specs.pb.go | 232 +++++++++ api/specs/specs.proto | 15 + api/specs/specs_vtproto.pb.go | 462 ++++++++++++++++++ cmd/omni-cloud-provider-qemu/main.go | 112 ++++- go.mod | 104 ++++ go.sum | 333 +++++++++++++ hack/release.sh | 2 +- hack/release.toml | 6 +- internal/controller/machine_request_status.go | 192 ++++++++ internal/debug/debug.go | 6 + internal/debug/disabled.go | 10 + internal/debug/enabled.go | 10 + internal/ipxe/ipxe.go | 199 ++++++++ internal/meta/meta.go | 9 + internal/provider/provider.go | 200 ++++++++ internal/provisioner/provisioner.go | 383 +++++++++++++++ internal/resources/qemu_machine.go | 53 ++ internal/resources/qemu_machine_allocation.go | 48 ++ internal/resources/resources.go | 17 + internal/version/data/sha | 1 + internal/version/data/tag | 1 + internal/version/version.go | 15 + 33 files changed, 2506 insertions(+), 35 deletions(-) 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 go.sum create mode 100644 internal/controller/machine_request_status.go create mode 100644 internal/debug/debug.go create mode 100644 internal/debug/disabled.go create mode 100644 internal/debug/enabled.go create mode 100644 internal/ipxe/ipxe.go create mode 100644 internal/meta/meta.go create mode 100644 internal/provider/provider.go create mode 100644 internal/provisioner/provisioner.go create mode 100644 internal/resources/qemu_machine.go create mode 100644 internal/resources/qemu_machine_allocation.go create mode 100644 internal/resources/resources.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 index 0d3a96a..ad617a8 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,6 +1,6 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-07-13T08:21:34Z by kres 8c8b007. +# Generated on 2024-06-21T07:15:12Z by kres 4c9f215. codecov: require_ci_to_pass: false diff --git a/.conform.yaml b/.conform.yaml index 4728824..b8ed479 100644 --- a/.conform.yaml +++ b/.conform.yaml @@ -1,6 +1,6 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-07-13T08:21:34Z by kres 8c8b007. +# Generated on 2024-06-25T08:36:21Z by kres 4c9f215. policies: - type: commit diff --git a/.dockerignore b/.dockerignore index 6a19875..5e3c0f2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,9 +1,11 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-07-13T08:21:34Z by kres 8c8b007. +# Generated on 2024-07-11T21:13:16Z by kres 8c8b007. * +!api !cmd +!internal !go.mod !go.sum !.golangci.yml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 956b010..fd17c4d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,6 +1,6 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-07-13T08:21:34Z by kres 8c8b007. +# Generated on 2024-06-25T08:36:21Z by kres 4c9f215. name: default concurrency: diff --git a/.github/workflows/slack-notify.yaml b/.github/workflows/slack-notify.yaml index 6f8cf2e..914cd19 100644 --- a/.github/workflows/slack-notify.yaml +++ b/.github/workflows/slack-notify.yaml @@ -1,6 +1,6 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-07-13T08:21:34Z by kres 8c8b007. +# Generated on 2024-06-21T07:15:12Z by kres 4c9f215. name: slack-notify "on": diff --git a/.gitignore b/.gitignore index 0d31644..fe61fe1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-07-13T08:21:34Z by kres 8c8b007. +# Generated on 2024-06-21T07:15:12Z by kres 4c9f215. _out diff --git a/.golangci.yml b/.golangci.yml index 9b9c7b9..a7b6086 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,6 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-07-13T08:21:34Z by kres 8c8b007. +# Generated on 2024-06-21T07:15:12Z by kres 4c9f215. # options for analysis running run: diff --git a/.kres.yaml b/.kres.yaml index dcaa8d7..3a6141c 100644 --- a/.kres.yaml +++ b/.kres.yaml @@ -2,6 +2,7 @@ kind: common.Image name: image-omni-cloud-provider-qemu spec: + baseImage: ghcr.io/siderolabs/talosctl:v1.7.5 pushLatest: false droneExtraEnvironment: PLATFORM: linux/amd64,linux/arm64 @@ -16,6 +17,16 @@ spec: GOOS: linux GOARCH: arm64 --- +kind: golang.Generate +spec: + versionPackagePath: internal/version + baseSpecPath: /api + vtProtobufEnabled: true + specs: + - source: api/specs/specs.proto + subdirectory: specs + genGateway: false +--- kind: common.Release name: release spec: diff --git a/.markdownlint.json b/.markdownlint.json index 95be063..1702812 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -1,6 +1,6 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-07-13T08:21:34Z by kres 8c8b007. +# Generated on 2024-07-11T21:13:16Z by kres 8c8b007. { "MD013": false, diff --git a/Dockerfile b/Dockerfile index 8514153..0fe503c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,12 +2,11 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-07-13T08:21:34Z by kres 8c8b007. +# Generated on 2024-07-11T21:20:23Z by kres 8c8b007. ARG TOOLCHAIN -# cleaned up specs and compiled versions -FROM scratch AS generate +FROM ghcr.io/siderolabs/talosctl:v1.7.5 AS base-image-omni-cloud-provider-qemu FROM ghcr.io/siderolabs/ca-certificates:v1.7.0 AS image-ca-certificates @@ -21,6 +20,10 @@ 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/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 @@ -35,6 +38,21 @@ 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 @@ -55,9 +73,27 @@ 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/specs/specs.proto +RUN rm /api/specs/specs.proto +RUN goimports -w -local github.com/siderolabs/omni-cloud-provider-qemu /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) @@ -75,33 +111,55 @@ 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 omni-cloud-provider-qemu-linux-amd64 FROM base AS omni-cloud-provider-qemu-linux-amd64-build COPY --from=generate / / +COPY --from=embed-generate / / WORKDIR /src/cmd/omni-cloud-provider-qemu ARG GO_BUILDFLAGS ARG GO_LDFLAGS -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}" -o /omni-cloud-provider-qemu-linux-amd64 +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=omni-cloud-provider-qemu -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG}" -o /omni-cloud-provider-qemu-linux-amd64 # builds omni-cloud-provider-qemu-linux-arm64 FROM base AS omni-cloud-provider-qemu-linux-arm64-build COPY --from=generate / / +COPY --from=embed-generate / / WORKDIR /src/cmd/omni-cloud-provider-qemu ARG GO_BUILDFLAGS ARG GO_LDFLAGS -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}" -o /omni-cloud-provider-qemu-linux-arm64 - -# 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} +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=omni-cloud-provider-qemu -X ${VERSION_PKG}.SHA=${SHA} -X ${VERSION_PKG}.Tag=${TAG}" -o /omni-cloud-provider-qemu-linux-arm64 FROM scratch AS omni-cloud-provider-qemu-linux-amd64 COPY --from=omni-cloud-provider-qemu-linux-amd64-build /omni-cloud-provider-qemu-linux-amd64 /omni-cloud-provider-qemu-linux-amd64 @@ -109,16 +167,13 @@ COPY --from=omni-cloud-provider-qemu-linux-amd64-build /omni-cloud-provider-qemu FROM scratch AS omni-cloud-provider-qemu-linux-arm64 COPY --from=omni-cloud-provider-qemu-linux-arm64-build /omni-cloud-provider-qemu-linux-arm64 /omni-cloud-provider-qemu-linux-arm64 -FROM scratch AS unit-tests -COPY --from=unit-tests-run /src/coverage.txt /coverage-unit-tests.txt - FROM omni-cloud-provider-qemu-linux-${TARGETARCH} AS omni-cloud-provider-qemu FROM scratch AS omni-cloud-provider-qemu-all COPY --from=omni-cloud-provider-qemu-linux-amd64 / / COPY --from=omni-cloud-provider-qemu-linux-arm64 / / -FROM scratch AS image-omni-cloud-provider-qemu +FROM base-image-omni-cloud-provider-qemu AS image-omni-cloud-provider-qemu ARG TARGETARCH COPY --from=omni-cloud-provider-qemu omni-cloud-provider-qemu-linux-${TARGETARCH} /omni-cloud-provider-qemu COPY --from=image-fhs / / diff --git a/Makefile b/Makefile index 569698b..ad0a71a 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-07-13T08:21:34Z by kres 8c8b007. +# Generated on 2024-07-11T21:13:16Z by kres 8c8b007. # common variables @@ -146,6 +146,9 @@ target-%: ## Builds the specified target defined in the Dockerfile. The build r local-%: ## Builds the specified target defined in the Dockerfile using the local output type. The build result will be output to the specified local destination. @$(MAKE) target-$* TARGET_ARGS="--output=type=local,dest=$(DEST) $(TARGET_ARGS)" +generate: ## Generate .proto definitions. + @$(MAKE) local-$@ DEST=./ + lint-golangci-lint: ## Runs golangci-lint linter. @$(MAKE) target-$@ diff --git a/api/specs/specs.pb.go b/api/specs/specs.pb.go new file mode 100644 index 0000000..dc2f206 --- /dev/null +++ b/api/specs/specs.pb.go @@ -0,0 +1,232 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.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 QemuMachineSpec struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Uuid string `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"` + DiskPath string `protobuf:"bytes,2,opt,name=disk_path,json=diskPath,proto3" json:"disk_path,omitempty"` +} + +func (x *QemuMachineSpec) Reset() { + *x = QemuMachineSpec{} + if protoimpl.UnsafeEnabled { + mi := &file_specs_specs_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *QemuMachineSpec) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*QemuMachineSpec) ProtoMessage() {} + +func (x *QemuMachineSpec) ProtoReflect() protoreflect.Message { + mi := &file_specs_specs_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use QemuMachineSpec.ProtoReflect.Descriptor instead. +func (*QemuMachineSpec) Descriptor() ([]byte, []int) { + return file_specs_specs_proto_rawDescGZIP(), []int{0} +} + +func (x *QemuMachineSpec) GetUuid() string { + if x != nil { + return x.Uuid + } + return "" +} + +func (x *QemuMachineSpec) GetDiskPath() string { + if x != nil { + return x.DiskPath + } + return "" +} + +type QemuMachineAllocationSpec struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TalosVersion string `protobuf:"bytes,1,opt,name=talos_version,json=talosVersion,proto3" json:"talos_version,omitempty"` + SchematicId string `protobuf:"bytes,2,opt,name=schematic_id,json=schematicId,proto3" json:"schematic_id,omitempty"` +} + +func (x *QemuMachineAllocationSpec) Reset() { + *x = QemuMachineAllocationSpec{} + if protoimpl.UnsafeEnabled { + mi := &file_specs_specs_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *QemuMachineAllocationSpec) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*QemuMachineAllocationSpec) ProtoMessage() {} + +func (x *QemuMachineAllocationSpec) ProtoReflect() protoreflect.Message { + mi := &file_specs_specs_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use QemuMachineAllocationSpec.ProtoReflect.Descriptor instead. +func (*QemuMachineAllocationSpec) Descriptor() ([]byte, []int) { + return file_specs_specs_proto_rawDescGZIP(), []int{1} +} + +func (x *QemuMachineAllocationSpec) GetTalosVersion() string { + if x != nil { + return x.TalosVersion + } + return "" +} + +func (x *QemuMachineAllocationSpec) GetSchematicId() string { + if x != nil { + return x.SchematicId + } + 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, 0x1f, 0x73, 0x69, 0x64, 0x65, 0x72, 0x6f, 0x2e, 0x6f, 0x6d, 0x6e, 0x69, + 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, + 0x70, 0x65, 0x63, 0x73, 0x22, 0x42, 0x0a, 0x0f, 0x51, 0x65, 0x6d, 0x75, 0x4d, 0x61, 0x63, 0x68, + 0x69, 0x6e, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x64, + 0x69, 0x73, 0x6b, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x64, 0x69, 0x73, 0x6b, 0x50, 0x61, 0x74, 0x68, 0x22, 0x63, 0x0a, 0x19, 0x51, 0x65, 0x6d, 0x75, + 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x53, 0x70, 0x65, 0x63, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x5f, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x61, + 0x6c, 0x6f, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x49, 0x64, 0x42, 0x3a, 0x5a, + 0x38, 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, 0x63, 0x6c, 0x6f, 0x75, + 0x64, 0x2d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2d, 0x71, 0x65, 0x6d, 0x75, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x73, 0x70, 0x65, 0x63, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_specs_specs_proto_rawDescOnce sync.Once + file_specs_specs_proto_rawDescData = file_specs_specs_proto_rawDesc +) + +func file_specs_specs_proto_rawDescGZIP() []byte { + file_specs_specs_proto_rawDescOnce.Do(func() { + file_specs_specs_proto_rawDescData = protoimpl.X.CompressGZIP(file_specs_specs_proto_rawDescData) + }) + return file_specs_specs_proto_rawDescData +} + +var file_specs_specs_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_specs_specs_proto_goTypes = []any{ + (*QemuMachineSpec)(nil), // 0: sidero.omni.discovery.api.specs.QemuMachineSpec + (*QemuMachineAllocationSpec)(nil), // 1: sidero.omni.discovery.api.specs.QemuMachineAllocationSpec +} +var file_specs_specs_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_specs_specs_proto_init() } +func file_specs_specs_proto_init() { + if File_specs_specs_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_specs_specs_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*QemuMachineSpec); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_specs_specs_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*QemuMachineAllocationSpec); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_specs_specs_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_specs_specs_proto_goTypes, + DependencyIndexes: file_specs_specs_proto_depIdxs, + MessageInfos: file_specs_specs_proto_msgTypes, + }.Build() + File_specs_specs_proto = out.File + file_specs_specs_proto_rawDesc = nil + file_specs_specs_proto_goTypes = nil + file_specs_specs_proto_depIdxs = nil +} diff --git a/api/specs/specs.proto b/api/specs/specs.proto new file mode 100644 index 0000000..198dbd8 --- /dev/null +++ b/api/specs/specs.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package sidero.omni.discovery.api.specs; + +option go_package = "github.com/siderolabs/omni-cloud-provider-qemu/api/specs"; + +message QemuMachineSpec { + string uuid = 1; + string disk_path = 2; +} + +message QemuMachineAllocationSpec { + string talos_version = 1; + string schematic_id = 2; +} diff --git a/api/specs/specs_vtproto.pb.go b/api/specs/specs_vtproto.pb.go new file mode 100644 index 0000000..98860d4 --- /dev/null +++ b/api/specs/specs_vtproto.pb.go @@ -0,0 +1,462 @@ +// 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 *QemuMachineSpec) CloneVT() *QemuMachineSpec { + if m == nil { + return (*QemuMachineSpec)(nil) + } + r := new(QemuMachineSpec) + r.Uuid = m.Uuid + r.DiskPath = m.DiskPath + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *QemuMachineSpec) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (m *QemuMachineAllocationSpec) CloneVT() *QemuMachineAllocationSpec { + if m == nil { + return (*QemuMachineAllocationSpec)(nil) + } + r := new(QemuMachineAllocationSpec) + r.TalosVersion = m.TalosVersion + r.SchematicId = m.SchematicId + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *QemuMachineAllocationSpec) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (this *QemuMachineSpec) EqualVT(that *QemuMachineSpec) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.Uuid != that.Uuid { + return false + } + if this.DiskPath != that.DiskPath { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *QemuMachineSpec) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*QemuMachineSpec) + if !ok { + return false + } + return this.EqualVT(that) +} +func (this *QemuMachineAllocationSpec) EqualVT(that *QemuMachineAllocationSpec) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.TalosVersion != that.TalosVersion { + return false + } + if this.SchematicId != that.SchematicId { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *QemuMachineAllocationSpec) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*QemuMachineAllocationSpec) + if !ok { + return false + } + return this.EqualVT(that) +} +func (m *QemuMachineSpec) 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 *QemuMachineSpec) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *QemuMachineSpec) 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.DiskPath) > 0 { + i -= len(m.DiskPath) + copy(dAtA[i:], m.DiskPath) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.DiskPath))) + i-- + dAtA[i] = 0x12 + } + if len(m.Uuid) > 0 { + i -= len(m.Uuid) + copy(dAtA[i:], m.Uuid) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Uuid))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QemuMachineAllocationSpec) 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 *QemuMachineAllocationSpec) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *QemuMachineAllocationSpec) 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.SchematicId) > 0 { + i -= len(m.SchematicId) + copy(dAtA[i:], m.SchematicId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.SchematicId))) + i-- + dAtA[i] = 0x12 + } + if len(m.TalosVersion) > 0 { + i -= len(m.TalosVersion) + copy(dAtA[i:], m.TalosVersion) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.TalosVersion))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QemuMachineSpec) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Uuid) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.DiskPath) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *QemuMachineAllocationSpec) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.TalosVersion) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.SchematicId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *QemuMachineSpec) 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: QemuMachineSpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QemuMachineSpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Uuid", 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.Uuid = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DiskPath", 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.DiskPath = 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 *QemuMachineAllocationSpec) 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: QemuMachineAllocationSpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QemuMachineAllocationSpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TalosVersion", 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.TalosVersion = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SchematicId", 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.SchematicId = 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/omni-cloud-provider-qemu/main.go b/cmd/omni-cloud-provider-qemu/main.go index cbad811..312ddfc 100644 --- a/cmd/omni-cloud-provider-qemu/main.go +++ b/cmd/omni-cloud-provider-qemu/main.go @@ -5,4 +5,114 @@ // Package main contains the entrypoint. package main -func 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-cloud-provider-qemu/internal/debug" + "github.com/siderolabs/omni-cloud-provider-qemu/internal/meta" + "github.com/siderolabs/omni-cloud-provider-qemu/internal/provider" + "github.com/siderolabs/omni-cloud-provider-qemu/internal/version" +) + +var rootCmdArgs struct { + omniAPIEndpoint string + talosctlPath string + imageFactoryPXEURL string + subnetCIDR string + + nameservers []string + + ipxeServerPort int + numMachines int + + clearState bool + debug bool +} + +// rootCmd represents the base command when called without any subcommands. +var rootCmd = &cobra.Command{ + Use: version.Name, + Short: "Provision QEMU VMs for Omni.", + Version: version.Tag, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + logger, err := initLogger() + if err != nil { + return fmt.Errorf("failed to create logger: %w", err) + } + + return run(cmd.Context(), logger) + }, +} + +func run(ctx context.Context, logger *zap.Logger) error { + omniServiceAccountKey := os.Getenv("OMNI_SERVICE_ACCOUNT_KEY") + + prov, err := provider.New(rootCmdArgs.omniAPIEndpoint, omniServiceAccountKey, rootCmdArgs.talosctlPath, rootCmdArgs.imageFactoryPXEURL, + rootCmdArgs.subnetCIDR, rootCmdArgs.nameservers, rootCmdArgs.numMachines, rootCmdArgs.ipxeServerPort, rootCmdArgs.clearState, logger) + if err != nil { + return fmt.Errorf("failed to create provider: %w", err) + } + + if err = prov.Run(ctx); err != nil { + return fmt.Errorf("failed to run provider: %w", err) + } + + return nil +} + +func initLogger() (*zap.Logger, error) { + var loggerConfig zap.Config + + if debug.Enabled { + loggerConfig = zap.NewDevelopmentConfig() + loggerConfig.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder + } else { + loggerConfig = zap.NewProductionConfig() + } + + if !rootCmdArgs.debug { + loggerConfig.Level.SetLevel(zap.InfoLevel) + } else { + loggerConfig.Level.SetLevel(zap.DebugLevel) + } + + return loggerConfig.Build(zap.AddStacktrace(zapcore.ErrorLevel)) +} + +func main() { + if err := runCmd(); err != nil { + log.Fatalf("failed to run: %v", err) + } +} + +func runCmd() error { + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, os.Interrupt) + defer cancel() + + return rootCmd.ExecuteContext(ctx) +} + +func init() { + rootCmd.Flags().StringVar(&rootCmdArgs.omniAPIEndpoint, "omni-api-endpoint", os.Getenv("OMNI_ENDPOINT"), + "the endpoint of the Omni API, if not set, defaults to OMNI_ENDPOINT env var.") + rootCmd.Flags().StringVar(&meta.ProviderID, "id", meta.ProviderID, "the id of the cloud provider, it is used to match the resources with the cloud provider label.") + rootCmd.Flags().StringVar(&rootCmdArgs.talosctlPath, "talosctl-path", "", "the path to the talosctl binary, when unset, "+ + "it is searched in the current working directory and in the $PATH.") + rootCmd.Flags().StringVar(&rootCmdArgs.subnetCIDR, "subnet-cidr", "172.42.0.0/24", "the CIDR of the subnet to use for the QEMU VMs.") + rootCmd.Flags().StringSliceVar(&rootCmdArgs.nameservers, "nameservers", []string{"1.1.1.1", "1.0.0.1"}, "the nameservers to use for the QEMU VMs.") + rootCmd.Flags().StringVar(&rootCmdArgs.imageFactoryPXEURL, "image-factory-pxe-url", "https://pxe.factory.talos.dev", "the URL of the image factory PXE server.") + rootCmd.Flags().IntVar(&rootCmdArgs.ipxeServerPort, "ipxe-server-port", 42420, "the port of the iPXE server.") + rootCmd.Flags().IntVar(&rootCmdArgs.numMachines, "num-machines", 8, "the number of machines to provision.") + rootCmd.Flags().BoolVar(&rootCmdArgs.clearState, "clear-state", false, "clear the state of the provider (for debugging purposes).") + rootCmd.Flags().BoolVar(&rootCmdArgs.debug, "debug", false, "enable debug logs.") +} diff --git a/go.mod b/go.mod index 3bb292a..42e4de2 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,107 @@ module github.com/siderolabs/omni-cloud-provider-qemu go 1.22.5 + +replace github.com/siderolabs/omni/client => github.com/utkuozdemir/sidero-omni/client v0.0.0-20240711101539-3fc6dbe61802 + +require ( + github.com/cosi-project/runtime v0.5.2 + github.com/google/uuid v1.6.0 + github.com/hashicorp/go-multierror v1.1.1 + github.com/planetscale/vtprotobuf v0.6.0 + github.com/siderolabs/gen v0.5.0 + github.com/siderolabs/net v0.4.0 + github.com/siderolabs/omni/client v0.39.0 + github.com/siderolabs/talos v1.7.5 + github.com/siderolabs/talos/pkg/machinery v1.7.5 + github.com/spf13/cobra v1.8.1 + go.uber.org/zap v1.27.0 + golang.org/x/sync v0.7.0 + google.golang.org/protobuf v1.34.2 +) + +require ( + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/ProtonMail/go-crypto v1.0.0 // indirect + github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect + github.com/ProtonMail/gopenpgp/v2 v2.7.5 // indirect + github.com/adrg/xdg v0.5.0 // indirect + github.com/alexflint/go-filemutex v1.3.0 // indirect + github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cloudflare/circl v1.3.9 // indirect + github.com/containerd/go-cni v1.1.10 // indirect + github.com/containernetworking/cni v1.2.2 // indirect + github.com/containernetworking/plugins v1.4.1 // indirect + github.com/coreos/go-iptables v0.7.0 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v26.0.2+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/felixge/httpsnoop v1.0.4 // 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/gogo/protobuf v1.3.2 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.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.1 // 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/insomniacslk/dhcp v0.0.0-20240227161007-c728f5dd21c8 // indirect + github.com/josharian/native v1.1.0 // indirect + github.com/jsimonetti/rtnetlink v1.4.2 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/mdlayher/ethtool v0.1.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/morikuni/aec v1.0.0 // 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.15 // indirect + github.com/pin/tftp/v3 v3.1.0 // 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.4.4 // indirect + github.com/siderolabs/go-api-signature v0.3.3 // indirect + github.com/siderolabs/go-blockdevice v0.4.7 // 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-tail v0.1.1 // indirect + github.com/siderolabs/protoenc v0.2.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a // indirect + github.com/ulikunitz/xz v0.5.12 // indirect + github.com/vishvananda/netlink v1.2.1-beta.2 // indirect + github.com/vishvananda/netns v0.0.4 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/sdk v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.27.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240708141625-4ad9e859172b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b // indirect + google.golang.org/grpc v1.65.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools/v3 v3.5.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..95a74c9 --- /dev/null +++ b/go.sum @@ -0,0 +1,333 @@ +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= +github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= +github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= +github.com/ProtonMail/gopenpgp/v2 v2.7.5 h1:STOY3vgES59gNgoOt2w0nyHBjKViB/qSg7NjbQWPJkA= +github.com/ProtonMail/gopenpgp/v2 v2.7.5/go.mod h1:IhkNEDaxec6NyzSI0PlxapinnwPVIESk8/76da3Ct3g= +github.com/adrg/xdg v0.5.0 h1:dDaZvhMXatArP1NPHhnfaQUqWBLBsmx1h1HXQdMoFCY= +github.com/adrg/xdg v0.5.0/go.mod h1:dDdY4M4DF9Rjy4kHPeNL+ilVF+p2lK8IdM9/rTSGcI4= +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/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/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4= +github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.9 h1:QFrlgFYf2Qpi8bSpVPK1HBvWpx16v/1TZivyo7pGuBE= +github.com/cloudflare/circl v1.3.9/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= +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.2 h1:9IbP6KJQQxVKo4hhnm8r50YcVKrJbJu3Dqw+Rbt1vYk= +github.com/containernetworking/cni v1.2.2/go.mod h1:DuLgF+aPd3DzcTQTtp/Nvl1Kim23oFKdm2okJzBQA5M= +github.com/containernetworking/plugins v1.4.1 h1:+sJRRv8PKhLkXIl6tH1D7RMi+CbbHutDGU+ErLBORWA= +github.com/containernetworking/plugins v1.4.1/go.mod h1:n6FFGKcaY4o2o5msgu/UImtoC+fpQXM3076VHfHbj60= +github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8= +github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= +github.com/cosi-project/runtime v0.5.2 h1:3UlNiXF1/jexLYu2grd8jdTR8/FrACOUdN1mxRnCKZs= +github.com/cosi-project/runtime v0.5.2/go.mod h1:m+bkfUzKYeUyoqYAQBxdce3bfgncG8BsqcbfKRbvJKs= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/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 v26.0.2+incompatible h1:yGVmKUFGgcxA6PXWAokO0sQL22BrQ67cgVjko8tGdXE= +github.com/docker/docker v26.0.2+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/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/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-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +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.1 h1:2JXqPZs1Jej67RtdTi0YZaEB2hEFB3fkBA4cPYKQwFQ= +github.com/hashicorp/go-getter/v2 v2.2.1/go.mod h1:EcJx6oZE8hmGuRR1l38QrfnyiujQbwsEAn11eHv6l2M= +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-20240227161007-c728f5dd21c8 h1:V3plQrMHRWOB5zMm3yNqvBxDQVW1+/wHBSok5uPdmVs= +github.com/insomniacslk/dhcp v0.0.0-20240227161007-c728f5dd21c8/go.mod h1:izxuNQZeFrbx2nK2fAyN5iNUB34Fe9j0nK4PwLzAkKw= +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 v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5GTEn90= +github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM= +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.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mdlayher/ethtool v0.1.0 h1:XAWHsmKhyPOo42qq/yTPb0eFBGUKKTR1rE0dVrWVQ0Y= +github.com/mdlayher/ethtool v0.1.0/go.mod h1:fBMLn2UhfRGtcH5ZFjr+6GUiHEjZsItFD7fSn7jbZVQ= +github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= +github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= +github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= +github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= +github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY= +github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4= +github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= +github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= +github.com/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.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= +github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/opencontainers/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.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pin/tftp/v3 v3.1.0 h1:rQaxd4pGwcAJnpId8zC+O2NX3B2/NscjDZQaqEjuE7c= +github.com/pin/tftp/v3 v3.1.0/go.mod h1:xwQaN4viYL019tM4i8iecm++5cGxSqen6AJEOEyEI0w= +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.0 h1:nBeETjudeJ5ZgBHUz1fVHvbqUKnYOXNhsIEabROxmNA= +github.com/planetscale/vtprotobuf v0.6.0/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/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.4.4 h1:Q6EDBMR2Ub2oAZW5Xl8lrKB27bM3Sn8Gkfw3rngco5U= +github.com/siderolabs/crypto v0.4.4/go.mod h1:hsR3tJ3aaeuhCChsLF4dBd9vlJVPvmhg4vvx2ez4aD4= +github.com/siderolabs/gen v0.5.0 h1:Afdjx+zuZDf53eH5DB+E+T2JeCwBXGinV66A6osLgQI= +github.com/siderolabs/gen v0.5.0/go.mod h1:1GUMBNliW98Xeq8GPQeVMYqQE09LFItE8enR3wgMh3Q= +github.com/siderolabs/go-api-signature v0.3.3 h1:CVtWdD/izGW5P+wfR8ac7eyRJKbKbduxOWMvTfpYU3A= +github.com/siderolabs/go-api-signature v0.3.3/go.mod h1:qp7De5ZrF021aPrhm5MyLPuaRhkiX4BADmZweqChw4I= +github.com/siderolabs/go-blockdevice v0.4.7 h1:2bk4WpEEflGxjrNwp57ye24Pr+cYgAiAeNMWiQOuWbQ= +github.com/siderolabs/go-blockdevice v0.4.7/go.mod h1:4PeOuk71pReJj1JQEXDE7kIIQJPVe8a+HZQa+qjxSEA= +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-tail v0.1.1 h1:3XeJgd97OHyFAIE7nQEMcRhOfnv7DvXbu0BRKbtT6u8= +github.com/siderolabs/go-tail v0.1.1/go.mod h1:IihAL39acadXHfb5fEAOKK2DaDFIrG2+VD3b2H/ziZ0= +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/protoenc v0.2.1 h1:BqxEmeWQeMpNP3R6WrPqDatX8sM/r4t97OP8mFmg6GA= +github.com/siderolabs/protoenc v0.2.1/go.mod h1:StTHxjet1g11GpNAWiATgc8K0HMKiFSEVVFOa/H0otc= +github.com/siderolabs/talos v1.7.5 h1:faUPlcI3tSM1THJMyE2g/O56mUeERdMWjN6whWiEt5k= +github.com/siderolabs/talos v1.7.5/go.mod h1:gUbJtqTAt6zUo/4vuZ9Sr9kOAy91mVvq2xncjJc/8IE= +github.com/siderolabs/talos/pkg/machinery v1.7.5 h1:M02UZSDfN0BB4bXhTYDjEmVvAIX1GsAS45cyKh6+HHU= +github.com/siderolabs/talos/pkg/machinery v1.7.5/go.mod h1:OeamhNo92c3V96bddZNhcCgoRyzw2KWBtpma1lfchtg= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a h1:BH1SOPEvehD2kVrndDnGJiUF0TrBpNs+iyYocu6h0og= +github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= +github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= +github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/utkuozdemir/sidero-omni/client v0.0.0-20240711101539-3fc6dbe61802 h1:T7dgR4f0mukga7lSTI/t0Y/gIrLGB2pL2VfsV+dW4lE= +github.com/utkuozdemir/sidero-omni/client v0.0.0-20240711101539-3fc6dbe61802/go.mod h1:d8KOAcGsJxTK7KMGzZPFn+M/8YF7swuV5nxCZUVeThQ= +github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs= +github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= +github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/yuin/goldmark v1.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.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 h1:j9+03ymgYhPKmeXGk5Zu+cIZOlVzd9Zv7QIiyItjFBU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0/go.mod h1:Y5+XiUG4Emn1hTfciPzGPJaSI+RpDts6BnCIir0SLqk= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +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.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +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/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +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-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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +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-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.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.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.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +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/genproto/googleapis/api v0.0.0-20240708141625-4ad9e859172b h1:y/kpOWeX2pWERnbsvh/hF+Zmo69wVmjyZhstreXQQeA= +google.golang.org/genproto/googleapis/api v0.0.0-20240708141625-4ad9e859172b/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b h1:04+jVzTs2XBnOZcPsLnmrTGqltqJbZQ1Ey26hjYdQQ0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.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= diff --git a/hack/release.sh b/hack/release.sh index 9cc5544..9e6c153 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -2,7 +2,7 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-07-13T08:21:34Z by kres 8c8b007. +# Generated on 2024-06-21T07:15:12Z by kres 4c9f215. set -e diff --git a/hack/release.toml b/hack/release.toml index ba044d6..2824ca3 100644 --- a/hack/release.toml +++ b/hack/release.toml @@ -1,9 +1,9 @@ # commit to be tagged for the new release commit = "HEAD" -project_name = "omni-cloud-provider-qemu" -github_repo = "siderolabs/omni-cloud-provider-qemu" -match_deps = "^github.com/(siderolabs/[a-zA-Z0-9-]+)$" +project_name = "" +github_repo = "/" +match_deps = "^github.com/(/[a-zA-Z0-9-]+)$" # previous = - # pre_release = true diff --git a/internal/controller/machine_request_status.go b/internal/controller/machine_request_status.go new file mode 100644 index 0000000..68582ea --- /dev/null +++ b/internal/controller/machine_request_status.go @@ -0,0 +1,192 @@ +// 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 controller provides the controller for the machine request status. +package controller + +import ( + "context" + "fmt" + "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/optional" + cloudspecs "github.com/siderolabs/omni/client/api/omni/specs/cloud" + "github.com/siderolabs/omni/client/pkg/omni/resources/cloud" + "go.uber.org/zap" + + "github.com/siderolabs/omni-cloud-provider-qemu/internal/meta" + "github.com/siderolabs/omni-cloud-provider-qemu/internal/provisioner" + "github.com/siderolabs/omni-cloud-provider-qemu/internal/resources" +) + +const ( + labelCloudProviderID = "omni.sidero.dev/cloud-provider-id" + requeueInterval = 30 * time.Second +) + +// MachineRequestStatusController creates the system config patch that contains the maintenance config. +type MachineRequestStatusController = qtransform.QController[*cloud.MachineRequest, *cloud.MachineRequestStatus] + +// NewMachineRequestStatusController initializes MachineRequestStatusController. +func NewMachineRequestStatusController(provisioner *provisioner.Provisioner) *MachineRequestStatusController { + return qtransform.NewQController( + qtransform.Settings[*cloud.MachineRequest, *cloud.MachineRequestStatus]{ + Name: "MachineRequestStatusController", + MapMetadataOptionalFunc: func(request *cloud.MachineRequest) optional.Optional[*cloud.MachineRequestStatus] { + providerID, ok := request.Metadata().Labels().Get(labelCloudProviderID) + + if ok && providerID == meta.ProviderID { + return optional.Some(cloud.NewMachineRequestStatus(request.Metadata().ID())) + } + + return optional.None[*cloud.MachineRequestStatus]() + }, + UnmapMetadataFunc: func(status *cloud.MachineRequestStatus) *cloud.MachineRequest { + return cloud.NewMachineRequest(status.Metadata().ID()) + }, + TransformExtraOutputFunc: func(ctx context.Context, r controller.ReaderWriter, logger *zap.Logger, request *cloud.MachineRequest, status *cloud.MachineRequestStatus) error { + status.Metadata().Labels().Set(labelCloudProviderID, meta.ProviderID) + + schematicID := request.TypedSpec().Value.SchematicId + talosVersion := request.TypedSpec().Value.TalosVersion + + logger.Info("received machine request", zap.String("schematic_id", schematicID), zap.String("talos_version", talosVersion)) + + machineUUID, isAllocated, err := findMachineUUID(ctx, r, request.Metadata().ID(), logger) + if err != nil { + return err + } + + if isAllocated { + status.TypedSpec().Value.Id = machineUUID + status.TypedSpec().Value.Stage = cloudspecs.MachineRequestStatusSpec_PROVISIONED + + return nil + } + + if machineUUID == "" { + logger.Info("no machine available yet, requeue") + + status.TypedSpec().Value.Stage = cloudspecs.MachineRequestStatusSpec_PROVISIONING + + return controller.NewRequeueInterval(requeueInterval) + } + + logger.Info("picked available qemu machine", zap.String("qemu_machine_id", machineUUID)) + + // allocate the machine + allocation := resources.NewQemuMachineAllocation(request.Metadata().ID()) + + allocation.Metadata().Labels().Set(resources.MachineUUIDLabel, machineUUID) + + allocation.TypedSpec().Value.TalosVersion = talosVersion + allocation.TypedSpec().Value.SchematicId = schematicID + + if err = r.Create(ctx, allocation); err != nil { + return fmt.Errorf("failed to create a QemuMachineAllocation: %w", err) + } + + status.TypedSpec().Value.Id = machineUUID + status.TypedSpec().Value.Stage = cloudspecs.MachineRequestStatusSpec_PROVISIONED + + return nil + }, + FinalizerRemovalExtraOutputFunc: func(ctx context.Context, r controller.ReaderWriter, logger *zap.Logger, request *cloud.MachineRequest) error { + allocation, err := r.Get(ctx, resources.NewQemuMachineAllocation(request.Metadata().ID()).Metadata()) + if err != nil { + if state.IsNotFoundError(err) { + return nil + } + + return err + } + + destroyReady, err := r.Teardown(ctx, allocation.Metadata()) + if err != nil { + return err + } + + if !destroyReady { + return controller.NewRequeueErrorf(requeueInterval, "allocation is not yet ready to be destroyed") + } + + if err = r.Destroy(ctx, allocation.Metadata()); err != nil { + return err + } + + machineUUID, ok := allocation.Metadata().Labels().Get(resources.MachineUUIDLabel) + if !ok { + logger.Warn("missing machine UUID label in the QemuMachineAllocation", zap.String("allocation_id", request.Metadata().ID())) + + return nil + } + + if err = provisioner.ResetMachine(ctx, machineUUID); err != nil { + return fmt.Errorf("failed to reset the machine: %w", err) + } + + return nil + }, + }, + qtransform.WithExtraOutputs( + controller.Output{ + Type: resources.QemuMachineAllocationType, + Kind: controller.OutputExclusive, + }, + ), + qtransform.WithExtraMappedInput( + qtransform.MapperNone[*resources.QemuMachine](), + ), + ) +} + +func findMachineUUID(ctx context.Context, r controller.Reader, requestID resource.ID, logger *zap.Logger) (machineUUID string, isAllocated bool, err error) { + qemuMachineList, err := safe.ReaderListAll[*resources.QemuMachine](ctx, r) + if err != nil { + return "", false, err + } + + machineUUIDToRequestID := make(map[string]resource.ID, qemuMachineList.Len()) + + allocationList, err := safe.ReaderListAll[*resources.QemuMachineAllocation](ctx, r) + if err != nil { + return "", false, err + } + + for iter := allocationList.Iterator(); iter.Next(); { + allocation := iter.Value() + + uuid, ok := allocation.Metadata().Labels().Get(resources.MachineUUIDLabel) + if !ok { + logger.Warn("missing machine UUID label in the QemuMachineAllocation", zap.String("allocation_id", allocation.Metadata().ID())) + + continue + } + + machineUUIDToRequestID[uuid] = allocation.Metadata().ID() + } + + for iter := qemuMachineList.Iterator(); iter.Next(); { + qemuMachine := iter.Value() + + if reqID, ok := machineUUIDToRequestID[qemuMachine.Metadata().ID()]; ok { + if reqID == requestID { // match, return + return qemuMachine.Metadata().ID(), true, nil + } + + // machine is allocated to another request, skip + continue + } + + // machine is available, return + return qemuMachine.Metadata().ID(), false, nil + } + + return "", false, nil +} diff --git a/internal/debug/debug.go b/internal/debug/debug.go new file mode 100644 index 0000000..70fd22d --- /dev/null +++ b/internal/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/debug/disabled.go b/internal/debug/disabled.go new file mode 100644 index 0000000..f8ef20f --- /dev/null +++ b/internal/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/debug/enabled.go b/internal/debug/enabled.go new file mode 100644 index 0000000..aa26e16 --- /dev/null +++ b/internal/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/ipxe/ipxe.go b/internal/ipxe/ipxe.go new file mode 100644 index 0000000..c0fb6eb --- /dev/null +++ b/internal/ipxe/ipxe.go @@ -0,0 +1,199 @@ +// 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 an iPXE server that serves iPXE scripts to boot machines. +package ipxe + +import ( + "bytes" + "context" + "errors" + "fmt" + "net" + "net/http" + "strconv" + "strings" + "text/template" + "time" + + "github.com/cosi-project/runtime/pkg/resource" + "github.com/cosi-project/runtime/pkg/safe" + "github.com/cosi-project/runtime/pkg/state" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "golang.org/x/sync/errgroup" + + "github.com/siderolabs/omni-cloud-provider-qemu/internal/resources" +) + +// Server is an iPXE server serving as a proxy to the image factory, capturing machine information before chaining to the image factory. +type Server struct { + logger *zap.Logger + state state.State + + // unallocatedMachineUUIDSet is used to control log verbosity. + unallocatedMachineUUIDSet map[string]struct{} + + imageFactoryPXEURL string + port int +} + +// NewServer creates a new iPXE server. +func NewServer(st state.State, imageFactoryPXEURL string, port int, logger *zap.Logger) *Server { + return &Server{ + logger: logger, + state: st, + imageFactoryPXEURL: imageFactoryPXEURL, + port: port, + + unallocatedMachineUUIDSet: map[string]struct{}{}, + } +} + +const ipxeScriptTemplate = `#!ipxe +chain --replace {{ .URL }}/pxe/{{ .SchematicID }}/{{ .TalosVersion }}/metal-amd64 +` + +// Start starts the iPXE server. +func (server *Server) Start(ctx context.Context) error { + eg, ctx := errgroup.WithContext(ctx) + + httpServer := &http.Server{ + Addr: net.JoinHostPort("", strconv.Itoa(server.port)), + Handler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + nodeUUID := request.URL.Query().Get("uuid") + logger := server.logger.With(zap.String("uuid", nodeUUID)) + ipxeRequestLogLevel := zapcore.InfoLevel + + if _, isUnallocated := server.unallocatedMachineUUIDSet[nodeUUID]; isUnallocated { + ipxeRequestLogLevel = zapcore.DebugLevel // reduce log level, as unallocated machines keep hitting iPXE endpoint repeatedly + } + + logger.Log(ipxeRequestLogLevel, "received iPXE request", zap.String("uuid", nodeUUID)) + + allocation, err := server.ensureQemuMachine(ctx, nodeUUID) + if err != nil { + server.handleError(writer, logger, fmt.Errorf("failed to ensure machine: %w", err)) + + return + } + + if allocation == nil { + server.unallocatedMachineUUIDSet[nodeUUID] = struct{}{} + + logger.Log(ipxeRequestLogLevel, "machine is not yet allocated to a request") + + writer.WriteHeader(http.StatusNotFound) + + if _, err = writer.Write([]byte("no pending requests, come later")); err != nil { + logger.Error("failed to write response", zap.Error(err)) + } + + return + } + + delete(server.unallocatedMachineUUIDSet, nodeUUID) + + logger.Info("matched machine to request", zap.String("machine_request_id", allocation.Metadata().ID())) + + tmpl, err := template.New("ipxe-script").Parse(ipxeScriptTemplate) + if err != nil { + server.handleError(writer, logger, fmt.Errorf("failed to parse iPXE script template: %w", err)) + + return + } + + talosVersion := allocation.TypedSpec().Value.TalosVersion + if !strings.HasPrefix(talosVersion, "v") { + talosVersion = "v" + talosVersion + } + + var buf bytes.Buffer + + if err = tmpl.Execute(&buf, struct { + URL string + SchematicID string + TalosVersion string + }{ + URL: server.imageFactoryPXEURL, + SchematicID: allocation.TypedSpec().Value.SchematicId, + TalosVersion: talosVersion, + }); err != nil { + server.handleError(writer, logger, fmt.Errorf("failed to execute iPXE script template: %w", err)) + } + + writer.Header().Set("Content-Type", "text/plain") + writer.WriteHeader(http.StatusOK) + + if _, err = writer.Write(buf.Bytes()); err != nil { + server.logger.Error("failed to write response", zap.Error(err)) + } + }), + } + + eg.Go(func() error { + <-ctx.Done() + + shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + if err := 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 := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + return fmt.Errorf("failed to run iPXE server: %w", err) + } + + return nil + }) + + return eg.Wait() +} + +//nolint:nilnil +func (server *Server) ensureQemuMachine(ctx context.Context, uuid string) (*resources.QemuMachineAllocation, error) { + res := resources.NewQemuMachine(uuid) + md := res.Metadata() + + // check if the machine has a matching allocation + allocationList, err := safe.StateListAll[*resources.QemuMachineAllocation](ctx, server.state, state.WithLabelQuery(resource.LabelEqual(resources.MachineUUIDLabel, uuid))) + if err != nil && !state.IsNotFoundError(err) { + return nil, err + } + + if allocationList.Len() > 0 { + return allocationList.Get(0), nil + } + + // create a qemu machine + existing, err := server.state.Get(ctx, md) + if err != nil && !state.IsNotFoundError(err) { + return nil, err + } + + if existing != nil { + return nil, nil + } + + if err = server.state.Create(ctx, res); err != nil { + return nil, fmt.Errorf("failed to create QemuMachine: %w", err) + } + + return nil, nil +} + +func (server *Server) handleError(w http.ResponseWriter, logger *zap.Logger, err error) { + logger.Error("internal server error", zap.Error(err)) + + w.WriteHeader(http.StatusInternalServerError) + + if _, err = w.Write([]byte("internal server error")); err != nil { + logger.Error("failed to write response", zap.Error(err)) + } +} diff --git a/internal/meta/meta.go b/internal/meta/meta.go new file mode 100644 index 0000000..3cca0c0 --- /dev/null +++ b/internal/meta/meta.go @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package meta contains meta information about the provider. +package meta + +// ProviderID is the ID of the provider. +var ProviderID = "qemu" diff --git a/internal/provider/provider.go b/internal/provider/provider.go new file mode 100644 index 0000000..a6d73ac --- /dev/null +++ b/internal/provider/provider.go @@ -0,0 +1,200 @@ +// 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 holds the main logic of the Omni cloud provider QEMU. +package provider + +import ( + "context" + "errors" + "fmt" + + "github.com/cosi-project/runtime/pkg/controller/runtime" + "github.com/cosi-project/runtime/pkg/resource" + "github.com/cosi-project/runtime/pkg/resource/protobuf" + "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/cloud" + "go.uber.org/zap" + "golang.org/x/sync/errgroup" + + "github.com/siderolabs/omni-cloud-provider-qemu/internal/controller" + "github.com/siderolabs/omni-cloud-provider-qemu/internal/ipxe" + "github.com/siderolabs/omni-cloud-provider-qemu/internal/meta" + "github.com/siderolabs/omni-cloud-provider-qemu/internal/provisioner" + "github.com/siderolabs/omni-cloud-provider-qemu/internal/resources" +) + +// Provider is the Omni cloud provider QEMU provider. +// +// It watches the MachineRequest resources and provisions them as QEMU VMs as needed. +type Provider struct { + logger *zap.Logger + provisioner *provisioner.Provisioner + omniEndpoint string + omniServiceAccountKey string + imageFactoryPXEURL string + ipxeServerPort int + + clear bool +} + +// New creates a new provider. +func New(omniEndpoint, omniServiceAccountKey, talosctlPath, imageFactoryPXEURL, subnetCIDR string, nameservers []string, + numMachines, ipxeServerPort int, clearState bool, logger *zap.Logger, +) (*Provider, error) { + if omniEndpoint == "" { + return nil, fmt.Errorf("omniEndpoint is required") + } + + if omniServiceAccountKey == "" { + return nil, fmt.Errorf("omni service account key is required") + } + + if logger == nil { + logger = zap.NewNop() + } + + clusterName := "omni-cloud-provider-" + meta.ProviderID + + qemuProvisioner, err := provisioner.New(talosctlPath, clusterName, subnetCIDR, nameservers, numMachines, ipxeServerPort, logger) + if err != nil { + return nil, fmt.Errorf("failed to create provisioner: %w", err) + } + + return &Provider{ + omniEndpoint: omniEndpoint, + omniServiceAccountKey: omniServiceAccountKey, + imageFactoryPXEURL: imageFactoryPXEURL, + ipxeServerPort: ipxeServerPort, + clear: clearState, + provisioner: qemuProvisioner, + logger: logger, + }, nil +} + +// Run runs the provider. +func (provider *Provider) Run(ctx context.Context) (runErr error) { + omniClient, err := provider.omniClient() + if err != nil { + return err + } + + omniState := omniClient.Omni().State() + + if provider.clear { + if err = provider.clearState(ctx, omniState); err != nil { + return fmt.Errorf("failed to clear provider state: %w", err) + } + } + + eg, ctx := errgroup.WithContext(ctx) + + eg.Go(func() error { + ipxeServerErr := ipxe.NewServer(omniState, provider.imageFactoryPXEURL, provider.ipxeServerPort, provider.logger).Start(ctx) + if ipxeServerErr != nil { + provider.logger.Error("failed to start iPXE server", zap.Error(ipxeServerErr)) + } + + return ipxeServerErr + }) + + eg.Go(func() error { + provisionErr := provider.provisioner.Provision(ctx) + if provisionErr != nil { + provider.logger.Error("failed to provision machines", zap.Error(provisionErr)) + } + + return provisionErr + }) + + defer func() { + runErr = errors.Join(runErr, omniClient.Close()) + }() + + if err = protobuf.RegisterResource(resources.QemuMachineType, &resources.QemuMachine{}); err != nil { + return fmt.Errorf("failed to register resource: %w", err) + } + + if err = protobuf.RegisterResource(resources.QemuMachineAllocationType, &resources.QemuMachineAllocation{}); err != nil { + return fmt.Errorf("failed to register resource: %w", err) + } + + cosiRuntime, err := runtime.NewRuntime(omniState, provider.logger) + if err != nil { + return fmt.Errorf("failed to create runtime: %w", err) + } + + if err = cosiRuntime.RegisterQController(controller.NewMachineRequestStatusController(provider.provisioner)); err != nil { + return fmt.Errorf("failed to register controller: %w", err) + } + + if err = cosiRuntime.Run(ctx); err != nil { + return fmt.Errorf("failed to run runtime: %w", err) + } + + return eg.Wait() +} + +func (provider *Provider) omniClient() (*client.Client, error) { + var cliOpts []client.Option + + if provider.omniServiceAccountKey != "" { + cliOpts = append(cliOpts, client.WithServiceAccount(provider.omniServiceAccountKey)) + } + + omniClient, err := client.New(provider.omniEndpoint, cliOpts...) + if err != nil { + return nil, fmt.Errorf("failed to create Omni client: %w", err) + } + + return omniClient, nil +} + +func (provider *Provider) clearState(ctx context.Context, st state.State) error { + provider.logger.Info("clearing provider state") + + if err := provider.provisioner.ClearState(ctx); err != nil { + return err + } + + statusList, err := st.List(ctx, cloud.NewMachineRequestStatus("").Metadata()) + if err != nil { + return err + } + + qemuMachineList, err := st.List(ctx, resources.NewQemuMachine("").Metadata()) + if err != nil { + return err + } + + qemuMachineAllocationList, err := st.List(ctx, resources.NewQemuMachineAllocation("").Metadata()) + if err != nil { + return err + } + + var errs error + + for _, list := range []resource.List{statusList, qemuMachineList, qemuMachineAllocationList} { + 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 + } + + provider.logger.Info("destroyed resource", zap.String("id", item.Metadata().ID())) + } + } + + return errs +} diff --git a/internal/provisioner/provisioner.go b/internal/provisioner/provisioner.go new file mode 100644 index 0000000..f7058b2 --- /dev/null +++ b/internal/provisioner/provisioner.go @@ -0,0 +1,383 @@ +// 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 provisioner implements the Omni cloud provider QEMU provisioner. +package provisioner + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "math/big" + "net" + "net/http" + "net/netip" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/google/uuid" + sideronet "github.com/siderolabs/net" + "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" + diskSize = 6 * 1024 * 1024 * 1024 +) + +// Provisioner is the Omni cloud provider QEMU provisioner. +// +// It watches the MachineRequest resources and provisions them as QEMU VMs as needed. +type Provisioner struct { + logger *zap.Logger + + subnetCIDR netip.Prefix + + talosctlPath string + stateDir string + cniDir string + clusterName string + + nameservers []netip.Addr + + numMachines int + ipxeServerPort int +} + +// New creates a new QEMU provisioner. +func New(talosctlPath, clusterName, subnetCIDR string, nameservers []string, numMachines, ipxeServerPort int, logger *zap.Logger) (*Provisioner, error) { + if logger == nil { + logger = zap.NewNop() + } + + talosDir, err := config.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", clusterName), zap.String("state_dir", stateDir)) + + if talosctlPath == "" { + if talosctlPath, err = findTalosctl(); err != nil { + return nil, fmt.Errorf("failed to find talosctl binary: %w", err) + } + } + + cidr, err := netip.ParsePrefix(subnetCIDR) + if err != nil { + return nil, fmt.Errorf("failed to parse subnet CIDR: %w", err) + } + + nss, err := parseNameservers(nameservers) + if err != nil { + return nil, fmt.Errorf("failed to parse nameservers: %w", err) + } + + return &Provisioner{ + talosctlPath: talosctlPath, + stateDir: stateDir, + cniDir: cniDir, + clusterName: clusterName, + subnetCIDR: cidr, + nameservers: nss, + ipxeServerPort: ipxeServerPort, + numMachines: numMachines, + logger: logger, + }, nil +} + +// Provision provisions a QEMU cluster with the given number of machines. If the cluster already exists, it is loaded instead. +func (provisioner *Provisioner) Provision(ctx context.Context) error { + qemuProvisioner, err := providers.Factory(ctx, providerName) + if err != nil { + return fmt.Errorf("failed to create provisioner: %w", err) + } + + gatewayAddr, err := sideronet.NthIPInNetwork(provisioner.subnetCIDR, 1) + if err != nil { + return fmt.Errorf("failed to get gateway address: %w", err) + } + + cniBundleURL := "https://github.com/siderolabs/talos/releases/download/v1.7.5/talosctl-cni-bundle-amd64.tar.gz" + ipxeBootScript := fmt.Sprintf("http://%s/ipxe?uuid=${uuid}", net.JoinHostPort(gatewayAddr.String(), strconv.Itoa(provisioner.ipxeServerPort))) + + existingCluster, err := qemuProvisioner.Reflect(ctx, provisioner.clusterName, provisioner.stateDir) + if err == nil { + provisioner.logger.Info("loaded existing cluster") + + if len(existingCluster.Info().Nodes) != provisioner.numMachines { + provisioner.logger.Warn("number of nodes in the existing cluster does not match the requested number of machines", + zap.Int("requested", provisioner.numMachines), + zap.Int("existing", len(existingCluster.Info().Nodes))) + } + + return nil + } + + if !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("failed to load existing cluster: %w", err) + } + + provisioner.logger.Info("create a new cluster") + + nodes := make([]provision.NodeRequest, 0, provisioner.numMachines) + + nanoCPUs, err := parseCPUShare("3") + if err != nil { + return fmt.Errorf("failed to parse CPU share: %w", err) + } + + for i := range provisioner.numMachines { + nodeUUID := uuid.New() + + provisioner.logger.Info("generated node UUID", zap.String("uuid", nodeUUID.String())) + + var ip netip.Addr + + ip, err = sideronet.NthIPInNetwork(provisioner.subnetCIDR, i+2) + if err != nil { + return fmt.Errorf("failed to calculate offset %d from CIDR %s: %w", i+2, provisioner.subnetCIDR, err) + } + + nodes = append(nodes, provision.NodeRequest{ + Name: nodeUUID.String(), + Type: machine.TypeWorker, + + IPs: []netip.Addr{ip}, + Memory: 3072 * 1024 * 1024, + NanoCPUs: nanoCPUs, + Disks: []*provision.Disk{ + {Size: diskSize}, + }, + SkipInjectingConfig: true, + UUID: &nodeUUID, + }) + } + + request := provision.ClusterRequest{ + Name: provisioner.clusterName, + + Network: provision.NetworkRequest{ + Name: provisioner.clusterName, + CIDRs: []netip.Prefix{provisioner.subnetCIDR}, + GatewayAddrs: []netip.Addr{gatewayAddr}, + MTU: 1500, + Nameservers: provisioner.nameservers, + LoadBalancerPorts: []int{6443}, + CNI: provision.CNIConfig{ + BinPath: []string{filepath.Join(provisioner.cniDir, "bin")}, + ConfDir: filepath.Join(provisioner.cniDir, "conf.d"), + CacheDir: filepath.Join(provisioner.cniDir, "cache"), + BundleURL: cniBundleURL, + }, + }, + + IPXEBootScript: ipxeBootScript, + SelfExecutable: provisioner.talosctlPath, + StateDirectory: provisioner.stateDir, + + Nodes: nodes, + } + + logWriter := zapio.Writer{ + Log: provisioner.logger, + Level: zapcore.InfoLevel, + } + defer logWriter.Close() //nolint:errcheck + + if _, err = qemuProvisioner.Create(ctx, request, + provision.WithBootlader(true), + provision.WithUEFI(true), + provision.WithLogWriter(&logWriter), + ); err != nil { + return fmt.Errorf("failed to create cluster: %w", err) + } + + <-ctx.Done() + + return nil +} + +// ClearState clears the state of the provisioner. +func (provisioner *Provisioner) ClearState(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, provisioner.clusterName, provisioner.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: provisioner.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 +} + +// ResetMachine resets the machine with the given UUID by wiping its disk and rebooting it. +func (provisioner *Provisioner) ResetMachine(ctx context.Context, uuid string) error { + logger := provisioner.logger.With(zap.String("uuid", uuid), zap.String("op", "reset")) + + launchConf, err := provisioner.readMachineLaunchConfig(uuid) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + logger.Warn("machine launch config not found, assume it is already reset") + + return nil + } + + return err + } + + if len(launchConf.DiskPaths) != 1 { + logger.Warn("unexpected number of disk paths", zap.Int("num_disk_paths", len(launchConf.DiskPaths))) + } + + if len(launchConf.DiskPaths) > 0 { + diskFile := launchConf.DiskPaths[0] + + logger.Info("wipe the disk file", zap.String("disk_file", diskFile)) + + if err = os.Truncate(diskFile, 0); err != nil { + return err + } + + if err = os.Truncate(diskFile, diskSize); err != nil { + return err + } + } + + if len(launchConf.GatewayAddrs) == 0 { + logger.Warn("no gateway address found") + + return nil + } + + gatewayAddr := launchConf.GatewayAddrs[0] + rebootEndpoint := "http://" + net.JoinHostPort(gatewayAddr.String(), strconv.Itoa(launchConf.APIPort)) + "/reboot" + + // make a post req to it + + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, rebootEndpoint, nil) + if err != nil { + return fmt.Errorf("failed to create reboot request: %w", err) + } + + logger.Info("rebooting machine", zap.String("reboot_endpoint", rebootEndpoint)) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return fmt.Errorf("failed to make reboot request: %w", err) + } + + defer resp.Body.Close() //nolint:errcheck + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected status code while resetting machine: %d", resp.StatusCode) + } + + return nil +} + +func (provisioner *Provisioner) readMachineLaunchConfig(uuid string) (launchConfig, error) { + configPath := filepath.Join(provisioner.stateDir, provisioner.clusterName, uuid+".config") + + configData, err := os.ReadFile(configPath) + if err != nil { + return launchConfig{}, fmt.Errorf("failed to read machine launch config JSON: %w", err) + } + + var conf launchConfig + + if err = json.Unmarshal(configData, &conf); err != nil { + return launchConfig{}, fmt.Errorf("failed to unmarshal machine launch config JSON: %w", err) + } + + return conf, nil +} + +type launchConfig struct { + DiskPaths []string + GatewayAddrs []netip.Addr + APIPort int +} + +func parseCPUShare(cpus string) (int64, error) { + cpu, ok := new(big.Rat).SetString(cpus) + if !ok { + return 0, fmt.Errorf("failed to parsing 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) { + name := "talosctl" + + if stat, err := os.Stat(name); err == nil && !stat.IsDir() { + return name, nil + } + + path, err := exec.LookPath(name) + 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/resources/qemu_machine.go b/internal/resources/qemu_machine.go new file mode 100644 index 0000000..0a5fd71 --- /dev/null +++ b/internal/resources/qemu_machine.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 resources + +import ( + "github.com/cosi-project/runtime/pkg/resource" + "github.com/cosi-project/runtime/pkg/resource/meta" + "github.com/cosi-project/runtime/pkg/resource/protobuf" + "github.com/cosi-project/runtime/pkg/resource/typed" + + "github.com/siderolabs/omni-cloud-provider-qemu/api/specs" +) + +// NewQemuMachine creates a new QemuMachine resource. +func NewQemuMachine(id string) *QemuMachine { + return typed.NewResource[QemuMachineSpec, QemuMachineExtension]( + resource.NewMetadata(namespace, QemuMachineType, id, resource.VersionUndefined), + protobuf.NewResourceSpec(&specs.QemuMachineSpec{}), + ) +} + +const ( + // QemuMachineType is the type of QemuMachine resource. + // + // tsgen:QemuMachineType + QemuMachineType = resource.Type("QemuMachines.omni-cloud-provider-qemu.sidero.dev") +) + +// QemuMachine resource describes a machine allocation. +type QemuMachine = typed.Resource[QemuMachineSpec, QemuMachineExtension] + +// QemuMachineSpec wraps specs.QemuMachineSpec. +type QemuMachineSpec = protobuf.ResourceSpec[specs.QemuMachineSpec, *specs.QemuMachineSpec] + +// QemuMachineExtension providers auxiliary methods for QemuMachine resource. +type QemuMachineExtension struct{} + +// ResourceDefinition implements [typed.Extension] interface. +func (QemuMachineExtension) ResourceDefinition() meta.ResourceDefinitionSpec { + return meta.ResourceDefinitionSpec{ + Type: QemuMachineType, + Aliases: []resource.Type{}, + DefaultNamespace: namespace, + PrintColumns: []meta.PrintColumn{ + { + Name: "UUID", + JSONPath: "{.uuid}", + }, + }, + } +} diff --git a/internal/resources/qemu_machine_allocation.go b/internal/resources/qemu_machine_allocation.go new file mode 100644 index 0000000..a60e041 --- /dev/null +++ b/internal/resources/qemu_machine_allocation.go @@ -0,0 +1,48 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package resources + +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-cloud-provider-qemu/api/specs" +) + +// NewQemuMachineAllocation creates a new QemuMachineAllocation resource. +func NewQemuMachineAllocation(id string) *QemuMachineAllocation { + return typed.NewResource[QemuMachineAllocationSpec, QemuMachineAllocationExtension]( + resource.NewMetadata(namespace, QemuMachineAllocationType, id, resource.VersionUndefined), + protobuf.NewResourceSpec(&specs.QemuMachineAllocationSpec{}), + ) +} + +const ( + // QemuMachineAllocationType is the type of QemuMachineAllocation resource. + // + // tsgen:QemuMachineAllocationType + QemuMachineAllocationType = resource.Type("QemuMachineAllocations.omni-cloud-provider-qemu.sidero.dev") +) + +// QemuMachineAllocation resource describes a machine allocation. +type QemuMachineAllocation = typed.Resource[QemuMachineAllocationSpec, QemuMachineAllocationExtension] + +// QemuMachineAllocationSpec wraps specs.QemuMachineAllocationSpec. +type QemuMachineAllocationSpec = protobuf.ResourceSpec[specs.QemuMachineAllocationSpec, *specs.QemuMachineAllocationSpec] + +// QemuMachineAllocationExtension providers auxiliary methods for QemuMachineAllocation resource. +type QemuMachineAllocationExtension struct{} + +// ResourceDefinition implements [typed.Extension] interface. +func (QemuMachineAllocationExtension) ResourceDefinition() meta.ResourceDefinitionSpec { + return meta.ResourceDefinitionSpec{ + Type: QemuMachineAllocationType, + Aliases: []resource.Type{}, + DefaultNamespace: namespace, + PrintColumns: []meta.PrintColumn{}, + } +} diff --git a/internal/resources/resources.go b/internal/resources/resources.go new file mode 100644 index 0000000..8b551ea --- /dev/null +++ b/internal/resources/resources.go @@ -0,0 +1,17 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package resources contains resource definitions of the omni-cloud-provider-qemu. +package resources + +import ( + "github.com/siderolabs/omni/client/pkg/omni/resources" + + "github.com/siderolabs/omni-cloud-provider-qemu/internal/meta" +) + +// MachineUUIDLabel is the label key for the machine UUID. +const MachineUUIDLabel = "machine-uuid" + +var namespace = resources.CloudProviderSpecificNamespacePrefix + meta.ProviderID 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..3f1426c --- /dev/null +++ b/internal/version/version.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 version provides version information. +package version + +var ( + // Name is set at build time. + Name string + // Tag is set at build time. + Tag string + // SHA is set at build time. + SHA string +)