diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6625b42..f90f888 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-09T15:22:07Z by kres 8c8b007. +# Generated on 2024-07-31T12:19:21Z by kres faf91e3. name: default concurrency: @@ -104,11 +104,37 @@ jobs: PUSH: "true" run: | make image-talemu IMAGE_TAG=latest + - name: talemu-cloud-provider + run: | + make talemu-cloud-provider + - name: Login to registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + password: ${{ secrets.GITHUB_TOKEN }} + registry: ghcr.io + username: ${{ github.repository_owner }} + - name: image-talemu-cloud-provider + run: | + make image-talemu-cloud-provider + - name: push-talemu-cloud-provider + if: github.event_name != 'pull_request' + env: + PUSH: "true" + run: | + make image-talemu-cloud-provider + - name: push-talemu-cloud-provider-latest + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' + env: + PUSH: "true" + run: | + make image-talemu-cloud-provider IMAGE_TAG=latest - name: Generate Checksums if: startsWith(github.ref, 'refs/tags/') run: | - sha256sum _out/talemu-* > _out/sha256sum.txt - sha512sum _out/talemu-* > _out/sha512sum.txt + cd _out + sha256sum talemu-* talemu-cloud-provider-* > sha256sum.txt + sha512sum talemu-* talemu-cloud-provider-* > sha512sum.txt - name: release-notes if: startsWith(github.ref, 'refs/tags/') run: | @@ -121,4 +147,5 @@ jobs: draft: "true" files: |- _out/talemu-* + _out/talemu-cloud-provider-* _out/sha*.txt diff --git a/Dockerfile b/Dockerfile index c4e5170..ae2e584 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -# syntax = docker/dockerfile-upstream:1.8.1-labs +# syntax = docker/dockerfile-upstream:1.9.0-labs # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-07-11T16:29:15Z by kres 8c8b007. +# Generated on 2024-07-31T12:19:21Z by kres faf91e3. ARG TOOLCHAIN @@ -11,7 +11,7 @@ FROM ghcr.io/siderolabs/ca-certificates:v1.7.0 AS image-ca-certificates FROM ghcr.io/siderolabs/fhs:v1.7.0 AS image-fhs # runs markdownlint -FROM docker.io/oven/bun:1.1.17-alpine AS lint-markdown +FROM docker.io/oven/bun:1.1.20-alpine AS lint-markdown WORKDIR /src RUN bun i markdownlint-cli@0.41.0 sentences-per-line@0.2.1 COPY .markdownlint.json . @@ -120,6 +120,14 @@ COPY --from=proto-compile /api/ /api/ FROM scratch AS unit-tests COPY --from=unit-tests-run /src/coverage.txt /coverage-unit-tests.txt +# builds talemu-cloud-provider-linux-amd64 +FROM base AS talemu-cloud-provider-linux-amd64-build +COPY --from=generate / / +WORKDIR /src/cmd/talemu-cloud-provider +ARG GO_BUILDFLAGS +ARG GO_LDFLAGS +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go build ${GO_BUILDFLAGS} -ldflags "${GO_LDFLAGS}" -o /talemu-cloud-provider-linux-amd64 + # builds talemu-linux-amd64 FROM base AS talemu-linux-amd64-build COPY --from=generate / / @@ -128,14 +136,30 @@ ARG GO_BUILDFLAGS ARG GO_LDFLAGS RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go build ${GO_BUILDFLAGS} -ldflags "${GO_LDFLAGS}" -o /talemu-linux-amd64 +FROM scratch AS talemu-cloud-provider-linux-amd64 +COPY --from=talemu-cloud-provider-linux-amd64-build /talemu-cloud-provider-linux-amd64 /talemu-cloud-provider-linux-amd64 + FROM scratch AS talemu-linux-amd64 COPY --from=talemu-linux-amd64-build /talemu-linux-amd64 /talemu-linux-amd64 +FROM talemu-cloud-provider-linux-${TARGETARCH} AS talemu-cloud-provider + +FROM scratch AS talemu-cloud-provider-all +COPY --from=talemu-cloud-provider-linux-amd64 / / + FROM talemu-linux-${TARGETARCH} AS talemu FROM scratch AS talemu-all COPY --from=talemu-linux-amd64 / / +FROM scratch AS image-talemu-cloud-provider +ARG TARGETARCH +COPY --from=talemu-cloud-provider talemu-cloud-provider-linux-${TARGETARCH} /talemu-cloud-provider +COPY --from=image-fhs / / +COPY --from=image-ca-certificates / / +LABEL org.opencontainers.image.source=https://github.com/siderolabs/talemu +ENTRYPOINT ["/talemu-cloud-provider"] + FROM scratch AS image-talemu ARG TARGETARCH COPY --from=talemu talemu-linux-${TARGETARCH} /talemu diff --git a/Makefile b/Makefile index ff06584..8918c9c 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-07-08T16:02:22Z by kres 8c8b007. +# Generated on 2024-07-31T12:19:21Z by kres faf91e3. # common variables @@ -21,11 +21,11 @@ PROTOBUF_GO_VERSION ?= 1.34.2 GRPC_GO_VERSION ?= 1.4.0 GRPC_GATEWAY_VERSION ?= 2.20.0 VTPROTOBUF_VERSION ?= 0.6.0 -GOIMPORTS_VERSION ?= 0.22.0 +GOIMPORTS_VERSION ?= 0.23.0 DEEPCOPY_VERSION ?= v0.5.6 GOLANGCILINT_VERSION ?= v1.59.1 GOFUMPT_VERSION ?= v0.6.0 -GO_VERSION ?= 1.22.4 +GO_VERSION ?= 1.22.5 GO_BUILDFLAGS ?= GO_LDFLAGS ?= CGO_ENABLED ?= 0 @@ -135,7 +135,7 @@ else GO_LDFLAGS += -s endif -all: unit-tests talemu image-talemu docker-compose-up docker-compose-down lint +all: unit-tests talemu image-talemu talemu-cloud-provider image-talemu-cloud-provider docker-compose-up docker-compose-down lint $(ARTIFACTS): ## Creates artifacts directory. @mkdir -p $(ARTIFACTS) @@ -203,6 +203,20 @@ lint: lint-golangci-lint lint-gofumpt lint-govulncheck lint-markdown ## Run all image-talemu: ## Builds image for talemu. @$(MAKE) target-$@ TARGET_ARGS="--tag=$(REGISTRY)/$(USERNAME)/talemu:$(IMAGE_TAG)" +.PHONY: $(ARTIFACTS)/talemu-cloud-provider-linux-amd64 +$(ARTIFACTS)/talemu-cloud-provider-linux-amd64: + @$(MAKE) local-talemu-cloud-provider-linux-amd64 DEST=$(ARTIFACTS) + +.PHONY: talemu-cloud-provider-linux-amd64 +talemu-cloud-provider-linux-amd64: $(ARTIFACTS)/talemu-cloud-provider-linux-amd64 ## Builds executable for talemu-cloud-provider-linux-amd64. + +.PHONY: talemu-cloud-provider +talemu-cloud-provider: talemu-cloud-provider-linux-amd64 ## Builds executables for talemu-cloud-provider. + +.PHONY: image-talemu-cloud-provider +image-talemu-cloud-provider: ## Builds image for talemu-cloud-provider. + @$(MAKE) target-$@ TARGET_ARGS="--tag=$(REGISTRY)/$(USERNAME)/talemu-cloud-provider:$(IMAGE_TAG)" + .PHONY: docker-compose-up docker-compose-up: ARTIFACTS="$(ARTIFACTS)" SHA="$(SHA)" TAG="$(TAG)" USERNAME="$(USERNAME)" REGISTRY="$(REGISTRY)" PROTOBUF_TS_VERSION="$(PROTOBUF_TS_VERSION)" NODE_BUILD_ARGS="$(NODE_BUILD_ARGS)" TOOLCHAIN="$(TOOLCHAIN)" CGO_ENABLED="$(CGO_ENABLED)" GO_BUILDFLAGS="$(GO_BUILDFLAGS)" GOLANGCILINT_VERSION="$(GOLANGCILINT_VERSION)" GOFUMPT_VERSION="$(GOFUMPT_VERSION)" GOIMPORTS_VERSION="$(GOIMPORTS_VERSION)" PROTOBUF_GO_VERSION="$(PROTOBUF_GO_VERSION)" GRPC_GO_VERSION="$(GRPC_GO_VERSION)" GRPC_GATEWAY_VERSION="$(GRPC_GATEWAY_VERSION)" VTPROTOBUF_VERSION="$(VTPROTOBUF_VERSION)" DEEPCOPY_VERSION="$(DEEPCOPY_VERSION)" TESTPKGS="$(TESTPKGS)" COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 GO_LDFLAGS="$(GO_LDFLAGS)" docker compose -p talemu --file ./hack/compose/docker-compose.yml --file ./hack/compose/docker-compose.override.yml up --build diff --git a/README.md b/README.md index 89129ab..9922f30 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ Runs multiple fake Talos nodes at the same time. To be used in pair with Omni. -## Running emulator +## Running Emulator Static Mode -Do `Copy Kernel Args` in the Omni UI, then paste the to `--kernel-args` flag. +Do `Copy Kernel Args` in the Omni UI. -Create `hack/compose/docker-compose.override.yml` file with the kernel args params. +Create `hack/compose/docker-compose.override.yml` and paste copied kernel args there. Final YAML file can look like this: @@ -24,3 +24,33 @@ services: Run `make docker-compose-up` command. This will spawn one hundred fake Talos nodes. + +## Cloud Provider Mode + +Run: + +```bash +make cloud-provider +``` + +Then run: + +```bash +sudo -E _out/talemu-cloud-provider-linux-amd64 --create-service-account --omni-api-endpoint=https://localhost:8099 +``` + +Create a machine request using `omnictl`: + +```yaml +metadata: + namespace: cloud-provider + type: MachineRequests.omni.sidero.dev + id: machine-1 + labels: + omni.sidero.dev/cloud-provider-id: talemu +spec: + talosversion: v1.7.5 + schematicid: 376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba +``` + +The machine should be created by the emulator and appear in Omni. diff --git a/api/specs/specs.pb.go b/api/specs/specs.pb.go index 8ab5e40..9b493a4 100644 --- a/api/specs/specs.pb.go +++ b/api/specs/specs.pb.go @@ -535,6 +535,78 @@ func (*RebootStatusSpec) Descriptor() ([]byte, []int) { return file_specs_specs_proto_rawDescGZIP(), []int{8} } +// MachineSpec started by the cloud provider emulator. +type MachineSpec struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Slot int32 `protobuf:"varint,1,opt,name=slot,proto3" json:"slot,omitempty"` + Uuid string `protobuf:"bytes,2,opt,name=uuid,proto3" json:"uuid,omitempty"` + Schematic string `protobuf:"bytes,3,opt,name=schematic,proto3" json:"schematic,omitempty"` + TalosVersion string `protobuf:"bytes,4,opt,name=talos_version,json=talosVersion,proto3" json:"talos_version,omitempty"` +} + +func (x *MachineSpec) Reset() { + *x = MachineSpec{} + if protoimpl.UnsafeEnabled { + mi := &file_specs_specs_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MachineSpec) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MachineSpec) ProtoMessage() {} + +func (x *MachineSpec) ProtoReflect() protoreflect.Message { + mi := &file_specs_specs_proto_msgTypes[9] + 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 MachineSpec.ProtoReflect.Descriptor instead. +func (*MachineSpec) Descriptor() ([]byte, []int) { + return file_specs_specs_proto_rawDescGZIP(), []int{9} +} + +func (x *MachineSpec) GetSlot() int32 { + if x != nil { + return x.Slot + } + return 0 +} + +func (x *MachineSpec) GetUuid() string { + if x != nil { + return x.Uuid + } + return "" +} + +func (x *MachineSpec) GetSchematic() string { + if x != nil { + return x.Schematic + } + return "" +} + +func (x *MachineSpec) GetTalosVersion() string { + if x != nil { + return x.TalosVersion + } + return "" +} + type ServiceSpec_Health struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -549,7 +621,7 @@ type ServiceSpec_Health struct { func (x *ServiceSpec_Health) Reset() { *x = ServiceSpec_Health{} if protoimpl.UnsafeEnabled { - mi := &file_specs_specs_proto_msgTypes[10] + mi := &file_specs_specs_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -562,7 +634,7 @@ func (x *ServiceSpec_Health) String() string { func (*ServiceSpec_Health) ProtoMessage() {} func (x *ServiceSpec_Health) ProtoReflect() protoreflect.Message { - mi := &file_specs_specs_proto_msgTypes[10] + mi := &file_specs_specs_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -610,78 +682,87 @@ 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, 0x05, 0x73, 0x70, 0x65, 0x63, 0x73, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc4, 0x01, 0x0a, 0x11, - 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x70, 0x65, - 0x63, 0x12, 0x22, 0x0a, 0x0c, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x70, 0x65, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, - 0x61, 0x70, 0x70, 0x65, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x5f, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x63, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, - 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x77, - 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x64, 0x65, 0x6e, 0x79, 0x5f, 0x65, - 0x74, 0x63, 0x64, 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x0f, 0x64, 0x65, 0x6e, 0x79, 0x45, 0x74, 0x63, 0x64, 0x4d, 0x65, 0x6d, 0x62, 0x65, - 0x72, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x6b, 0x75, 0x62, 0x65, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6b, 0x75, 0x62, 0x65, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x22, 0x73, 0x0a, 0x11, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x53, 0x70, 0x65, 0x63, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x65, 0x74, 0x63, 0x64, 0x5f, 0x6d, 0x65, - 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, - 0x74, 0x63, 0x64, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x68, - 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, - 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x96, 0x01, 0x0a, 0x12, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x53, 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x43, - 0x0a, 0x08, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x27, 0x2e, 0x73, 0x70, 0x65, 0x63, 0x73, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x69, - 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x70, 0x65, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x73, 0x1a, 0x3b, 0x0a, 0x0d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x22, 0x47, 0x0a, 0x0b, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x70, 0x65, 0x63, 0x12, - 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, - 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x72, 0x63, - 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x22, 0x43, 0x0a, 0x09, 0x49, 0x6d, 0x61, - 0x67, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x22, 0x3d, - 0x0a, 0x0f, 0x43, 0x61, 0x63, 0x68, 0x65, 0x64, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x53, 0x70, 0x65, - 0x63, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x22, 0x85, 0x02, - 0x0a, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, - 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, - 0x61, 0x74, 0x65, 0x12, 0x31, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x70, 0x65, 0x63, 0x73, 0x2e, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, - 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x1a, 0x9c, 0x01, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x6c, 0x74, - 0x68, 0x12, 0x18, 0x0a, 0x07, 0x75, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x07, 0x75, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x68, - 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x65, - 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6c, 0x61, 0x73, - 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, - 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x43, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x43, 0x0a, 0x0a, 0x52, 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x53, - 0x70, 0x65, 0x63, 0x12, 0x35, 0x0a, 0x08, 0x64, 0x6f, 0x77, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x08, 0x64, 0x6f, 0x77, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x52, 0x65, - 0x62, 0x6f, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x70, 0x65, 0x63, 0x42, 0x28, - 0x5a, 0x26, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, 0x64, - 0x65, 0x72, 0x6f, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x74, 0x61, 0x6c, 0x65, 0x6d, 0x75, 0x2f, 0x61, - 0x70, 0x69, 0x2f, 0x73, 0x70, 0x65, 0x63, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x65, 0x6d, 0x75, 0x73, 0x70, 0x65, 0x63, 0x73, 0x1a, 0x1f, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, + 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc4, + 0x01, 0x0a, 0x11, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x53, 0x70, 0x65, 0x63, 0x12, 0x22, 0x0a, 0x0c, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, + 0x70, 0x70, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x62, 0x6f, 0x6f, 0x74, + 0x73, 0x74, 0x72, 0x61, 0x70, 0x70, 0x65, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x5f, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x73, 0x12, + 0x18, 0x0a, 0x07, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x07, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x64, 0x65, 0x6e, + 0x79, 0x5f, 0x65, 0x74, 0x63, 0x64, 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x04, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x64, 0x65, 0x6e, 0x79, 0x45, 0x74, 0x63, 0x64, 0x4d, 0x65, + 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x6b, 0x75, 0x62, 0x65, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6b, 0x75, 0x62, 0x65, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x73, 0x0a, 0x11, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x70, 0x65, 0x63, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x65, 0x74, 0x63, 0x64, + 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x65, 0x74, 0x63, 0x64, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, + 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x99, 0x01, 0x0a, 0x12, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x53, 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x70, 0x65, + 0x63, 0x12, 0x46, 0x0a, 0x08, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x65, 0x6d, 0x75, 0x73, 0x70, 0x65, 0x63, 0x73, 0x2e, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x53, 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x70, 0x65, + 0x63, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x08, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x3b, 0x0a, 0x0d, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x47, 0x0a, 0x0b, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x53, 0x70, 0x65, 0x63, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x61, + 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x22, + 0x43, 0x0a, 0x09, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x18, 0x0a, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x74, 0x69, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x74, 0x69, 0x63, 0x22, 0x3d, 0x0a, 0x0f, 0x43, 0x61, 0x63, 0x68, 0x65, 0x64, 0x49, 0x6d, + 0x61, 0x67, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, + 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, + 0x69, 0x7a, 0x65, 0x22, 0x88, 0x02, 0x0a, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, + 0x70, 0x65, 0x63, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x68, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x65, 0x6d, 0x75, 0x73, + 0x70, 0x65, 0x63, 0x73, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, + 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x1a, + 0x9c, 0x01, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x75, 0x6e, + 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x75, 0x6e, 0x6b, + 0x6e, 0x6f, 0x77, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x21, + 0x0a, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x43, + 0x0a, 0x0a, 0x52, 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x53, 0x70, 0x65, 0x63, 0x12, 0x35, 0x0a, 0x08, + 0x64, 0x6f, 0x77, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x64, 0x6f, 0x77, 0x6e, 0x74, + 0x69, 0x6d, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x52, 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x53, 0x70, 0x65, 0x63, 0x22, 0x78, 0x0a, 0x0b, 0x4d, 0x61, 0x63, 0x68, 0x69, + 0x6e, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x6f, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x73, 0x6c, 0x6f, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x12, 0x1c, + 0x0a, 0x09, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x12, 0x23, 0x0a, 0x0d, + 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x42, 0x28, 0x5a, 0x26, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x73, 0x69, 0x64, 0x65, 0x72, 0x6f, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x74, 0x61, 0x6c, 0x65, 0x6d, + 0x75, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x70, 0x65, 0x63, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -696,27 +777,28 @@ func file_specs_specs_proto_rawDescGZIP() []byte { return file_specs_specs_proto_rawDescData } -var file_specs_specs_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_specs_specs_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_specs_specs_proto_goTypes = []any{ - (*ClusterStatusSpec)(nil), // 0: specs.ClusterStatusSpec - (*MachineStatusSpec)(nil), // 1: specs.MachineStatusSpec - (*EventSinkStateSpec)(nil), // 2: specs.EventSinkStateSpec - (*VersionSpec)(nil), // 3: specs.VersionSpec - (*ImageSpec)(nil), // 4: specs.ImageSpec - (*CachedImageSpec)(nil), // 5: specs.CachedImageSpec - (*ServiceSpec)(nil), // 6: specs.ServiceSpec - (*RebootSpec)(nil), // 7: specs.RebootSpec - (*RebootStatusSpec)(nil), // 8: specs.RebootStatusSpec - nil, // 9: specs.EventSinkStateSpec.VersionsEntry - (*ServiceSpec_Health)(nil), // 10: specs.ServiceSpec.Health - (*durationpb.Duration)(nil), // 11: google.protobuf.Duration - (*timestamppb.Timestamp)(nil), // 12: google.protobuf.Timestamp + (*ClusterStatusSpec)(nil), // 0: emuspecs.ClusterStatusSpec + (*MachineStatusSpec)(nil), // 1: emuspecs.MachineStatusSpec + (*EventSinkStateSpec)(nil), // 2: emuspecs.EventSinkStateSpec + (*VersionSpec)(nil), // 3: emuspecs.VersionSpec + (*ImageSpec)(nil), // 4: emuspecs.ImageSpec + (*CachedImageSpec)(nil), // 5: emuspecs.CachedImageSpec + (*ServiceSpec)(nil), // 6: emuspecs.ServiceSpec + (*RebootSpec)(nil), // 7: emuspecs.RebootSpec + (*RebootStatusSpec)(nil), // 8: emuspecs.RebootStatusSpec + (*MachineSpec)(nil), // 9: emuspecs.MachineSpec + nil, // 10: emuspecs.EventSinkStateSpec.VersionsEntry + (*ServiceSpec_Health)(nil), // 11: emuspecs.ServiceSpec.Health + (*durationpb.Duration)(nil), // 12: google.protobuf.Duration + (*timestamppb.Timestamp)(nil), // 13: google.protobuf.Timestamp } var file_specs_specs_proto_depIdxs = []int32{ - 9, // 0: specs.EventSinkStateSpec.versions:type_name -> specs.EventSinkStateSpec.VersionsEntry - 10, // 1: specs.ServiceSpec.health:type_name -> specs.ServiceSpec.Health - 11, // 2: specs.RebootSpec.downtime:type_name -> google.protobuf.Duration - 12, // 3: specs.ServiceSpec.Health.last_change:type_name -> google.protobuf.Timestamp + 10, // 0: emuspecs.EventSinkStateSpec.versions:type_name -> emuspecs.EventSinkStateSpec.VersionsEntry + 11, // 1: emuspecs.ServiceSpec.health:type_name -> emuspecs.ServiceSpec.Health + 12, // 2: emuspecs.RebootSpec.downtime:type_name -> google.protobuf.Duration + 13, // 3: emuspecs.ServiceSpec.Health.last_change:type_name -> google.protobuf.Timestamp 4, // [4:4] is the sub-list for method output_type 4, // [4:4] is the sub-list for method input_type 4, // [4:4] is the sub-list for extension type_name @@ -838,7 +920,19 @@ func file_specs_specs_proto_init() { return nil } } - file_specs_specs_proto_msgTypes[10].Exporter = func(v any, i int) any { + file_specs_specs_proto_msgTypes[9].Exporter = func(v any, i int) any { + switch v := v.(*MachineSpec); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_specs_specs_proto_msgTypes[11].Exporter = func(v any, i int) any { switch v := v.(*ServiceSpec_Health); i { case 0: return &v.state @@ -857,7 +951,7 @@ func file_specs_specs_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_specs_specs_proto_rawDesc, NumEnums: 0, - NumMessages: 11, + NumMessages: 12, NumExtensions: 0, NumServices: 0, }, diff --git a/api/specs/specs.proto b/api/specs/specs.proto index c4c7d5f..e9f0a83 100644 --- a/api/specs/specs.proto +++ b/api/specs/specs.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -package specs; +package emuspecs; option go_package = "github.com/siderolabs/talemu/api/specs"; @@ -67,3 +67,11 @@ message RebootSpec { // RebootStatusSpec is generated for each reboot spec. message RebootStatusSpec {} + +// MachineSpec started by the cloud provider emulator. +message MachineSpec { + int32 slot = 1; + string uuid = 2; + string schematic = 3; + string talos_version = 4; +} diff --git a/api/specs/specs_vtproto.pb.go b/api/specs/specs_vtproto.pb.go index b4f9cdb..5a147bc 100644 --- a/api/specs/specs_vtproto.pb.go +++ b/api/specs/specs_vtproto.pb.go @@ -225,6 +225,26 @@ func (m *RebootStatusSpec) CloneMessageVT() proto.Message { return m.CloneVT() } +func (m *MachineSpec) CloneVT() *MachineSpec { + if m == nil { + return (*MachineSpec)(nil) + } + r := new(MachineSpec) + r.Slot = m.Slot + r.Uuid = m.Uuid + r.Schematic = m.Schematic + r.TalosVersion = m.TalosVersion + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *MachineSpec) CloneMessageVT() proto.Message { + return m.CloneVT() +} + func (this *ClusterStatusSpec) EqualVT(that *ClusterStatusSpec) bool { if this == that { return true @@ -475,6 +495,34 @@ func (this *RebootStatusSpec) EqualMessageVT(thatMsg proto.Message) bool { } return this.EqualVT(that) } +func (this *MachineSpec) EqualVT(that *MachineSpec) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.Slot != that.Slot { + return false + } + if this.Uuid != that.Uuid { + return false + } + if this.Schematic != that.Schematic { + return false + } + if this.TalosVersion != that.TalosVersion { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *MachineSpec) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*MachineSpec) + if !ok { + return false + } + return this.EqualVT(that) +} func (m *ClusterStatusSpec) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -992,6 +1040,65 @@ func (m *RebootStatusSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *MachineSpec) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MachineSpec) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *MachineSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.TalosVersion) > 0 { + i -= len(m.TalosVersion) + copy(dAtA[i:], m.TalosVersion) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.TalosVersion))) + i-- + dAtA[i] = 0x22 + } + if len(m.Schematic) > 0 { + i -= len(m.Schematic) + copy(dAtA[i:], m.Schematic) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Schematic))) + i-- + dAtA[i] = 0x1a + } + 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] = 0x12 + } + if m.Slot != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Slot)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *ClusterStatusSpec) SizeVT() (n int) { if m == nil { return 0 @@ -1186,6 +1293,31 @@ func (m *RebootStatusSpec) SizeVT() (n int) { return n } +func (m *MachineSpec) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Slot != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Slot)) + } + l = len(m.Uuid) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Schematic) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.TalosVersion) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + func (m *ClusterStatusSpec) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -2452,3 +2584,169 @@ func (m *RebootStatusSpec) UnmarshalVT(dAtA []byte) error { } return nil } +func (m *MachineSpec) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MachineSpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MachineSpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Slot", wireType) + } + m.Slot = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Slot |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + 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 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Schematic", 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.Schematic = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + 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 + 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/talemu-cloud-provider/main.go b/cmd/talemu-cloud-provider/main.go new file mode 100644 index 0000000..5ceadb0 --- /dev/null +++ b/cmd/talemu-cloud-provider/main.go @@ -0,0 +1,203 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package main is the root cmd of the Talemu script. +package main + +import ( + "context" + "errors" + "os" + "os/signal" + "slices" + "syscall" + "time" + + "github.com/siderolabs/go-api-signature/pkg/pgp" + "github.com/siderolabs/go-api-signature/pkg/serviceaccount" + "github.com/siderolabs/omni/client/api/omni/management" + "github.com/siderolabs/omni/client/pkg/access" + "github.com/siderolabs/omni/client/pkg/omni/resources" + "github.com/spf13/cobra" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + emuruntime "github.com/siderolabs/talemu/internal/pkg/emu" + "github.com/siderolabs/talemu/internal/pkg/kubefactory" + "github.com/siderolabs/talemu/internal/pkg/machine/runtime" + "github.com/siderolabs/talemu/internal/pkg/machine/runtime/resources/emu" + "github.com/siderolabs/talemu/internal/pkg/provider" + "github.com/siderolabs/talemu/internal/pkg/provider/clientconfig" + "github.com/siderolabs/talemu/internal/pkg/provider/meta" +) + +// rootCmd represents the base command when called without any subcommands. +var rootCmd = &cobra.Command{ + Use: "talemu-cloud-provider", + Short: "Talos emulator cloud provider", + Long: `Connects to Omni as a cloud provider and creates/removes machines for MachineRequests`, + SilenceUsage: true, + RunE: func(cmd *cobra.Command, _ []string) error { + loggerConfig := zap.NewDevelopmentConfig() + loggerConfig.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder + + logger, err := loggerConfig.Build( + zap.AddStacktrace(zapcore.ErrorLevel), + ) + if err != nil { + return err + } + + if cfg.createServiceAccount { + err = createServiceAccount(cmd.Context()) + if err != nil { + return err + } + } + + providerState, err := provider.NewState(cfg.omniAPIEndpoint, cfg.serviceAccountKey) + if err != nil { + return err + } + + defer providerState.Close() //nolint:errcheck + + if err = os.MkdirAll("_out/state", 0o755); err != nil { + if !errors.Is(err, os.ErrExist) { + return err + } + } + + emulatorState, backingStore, err := runtime.NewState("_out/state/emulator.db", logger, runtime.NamespacedState{ + Namespace: resources.CloudProviderSpecificNamespacePrefix + meta.ProviderID, + State: providerState.State(), + }, runtime.NamespacedState{ + Namespace: resources.CloudProviderNamespace, + State: providerState.State(), + }, runtime.NamespacedState{ + Namespace: resources.DefaultNamespace, + State: providerState.State(), + }) + if err != nil { + return err + } + + defer backingStore.Close() //nolint:errcheck + + if err = emu.Register(cmd.Context(), emulatorState); err != nil { + return err + } + + kubernetes, err := kubefactory.New(cmd.Context(), "_out/state", logger) + if err != nil { + return err + } + + runtime, err := emuruntime.NewRuntime(emulatorState, kubernetes, logger) + if err != nil { + return err + } + + if err = provider.RegisterControllers(runtime, kubernetes); err != nil { + return err + } + + return runtime.Run(cmd.Context()) + }, +} + +func createServiceAccount(ctx context.Context) error { + config := clientconfig.New(cfg.omniAPIEndpoint) + + rootClient, err := config.GetClient() + if err != nil { + return err + } + + name := access.CloudProviderServiceAccountPrefix + meta.ProviderID + + sa := access.ParseServiceAccountFromName(name) + + key, err := pgp.GenerateKey(sa.BaseName, "", sa.FullID(), 365*24*time.Hour) + if err != nil { + return err + } + + armoredPublicKey, err := key.ArmorPublic() + if err != nil { + return err + } + + cfg.serviceAccountKey, err = serviceaccount.Encode(name, key) + if err != nil { + return err + } + + accounts, err := rootClient.Management().ListServiceAccounts(ctx) + if err != nil { + return err + } + + if slices.ContainsFunc(accounts, func(acc *management.ListServiceAccountsResponse_ServiceAccount) bool { + return acc.Name == name + }) { + err = rootClient.Management().DestroyServiceAccount(ctx, name) + if err != nil { + return err + } + } + + // create service account with the generated key + _, err = rootClient.Management().CreateServiceAccount(ctx, name, armoredPublicKey, "CloudProvider", false) + + return err +} + +var cfg struct { + omniAPIEndpoint string + serviceAccountKey string + kernelArgs string + createServiceAccount bool +} + +func main() { + ctx := context.Background() + + ctx, cancel := context.WithCancel(ctx) + + signals := make(chan os.Signal, 1) + + signal.Notify(signals, os.Interrupt, syscall.SIGHUP, syscall.SIGTERM) + + exitCode := 0 + + defer func() { + signal.Stop(signals) + cancel() + + os.Exit(exitCode) + }() + + go func() { + select { + case <-signals: + cancel() + + case <-ctx.Done(): + } + }() + + if err := rootCmd.ExecuteContext(ctx); err != nil { + exitCode = 1 + } +} + +func init() { + rootCmd.Flags().StringVar(&cfg.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(&cfg.serviceAccountKey, "key", os.Getenv("OMNI_SERVICE_ACCOUNT_KEY"), "Omni service account key, if not set, defaults to OMNI_SERVICE_ACCOUNT_KEY.") + rootCmd.Flags().BoolVar(&cfg.createServiceAccount, "create-service-account", false, + "try creating service account for itself (works only if Omni is running in debug mode)") +} diff --git a/cmd/talemu/main.go b/cmd/talemu/main.go index f66d38c..27cc6d2 100644 --- a/cmd/talemu/main.go +++ b/cmd/talemu/main.go @@ -9,14 +9,10 @@ import ( "context" "errors" "fmt" - "net/url" "os" "os/signal" - "regexp" "syscall" - "github.com/siderolabs/go-procfs/procfs" - "github.com/siderolabs/talos/pkg/machinery/constants" "github.com/spf13/cobra" "go.uber.org/multierr" "go.uber.org/zap" @@ -37,30 +33,11 @@ var rootCmd = &cobra.Command{ Long: `Can simulate as many nodes as you want`, SilenceUsage: true, RunE: func(cmd *cobra.Command, _ []string) error { - if cfg.kernelArgs == "" { - endpoint, err := parseSiderolinkEndpoint(cfg.apiEndpoint) - if err != nil { - return err - } - - cfg.apiEndpoint = endpoint.apiEndpoint - cfg.insecure = endpoint.insecure - } - - if err := parseKernelArgs(cfg.kernelArgs); err != nil { + params, err := machine.ParseKernelArgs(cfg.kernelArgs) + if err != nil { return err } - params := &machine.SideroLinkParams{ - Host: cfg.apiEndpoint, - APIEndpoint: cfg.apiEndpoint, - JoinToken: cfg.joinToken, - Insecure: cfg.insecure, - EventsEndpoint: cfg.eventsEndpoint, - LogsEndpoint: cfg.logsEndpoint, - TunnelMode: cfg.tunnelMode, - } - eg, ctx := errgroup.WithContext(cmd.Context()) machines := make([]*machine.Machine, 0, cfg.machinesCount) @@ -75,7 +52,7 @@ var rootCmd = &cobra.Command{ return err } - if err = os.MkdirAll("_out/state", 0o664); err != nil { + if err = os.MkdirAll("_out/state", 0o755); err != nil { if !errors.Is(err, os.ErrExist) { return err } @@ -164,18 +141,8 @@ var rootCmd = &cobra.Command{ } var cfg struct { - wireguardEndpoint string - apiEndpoint string - joinToken string - eventsEndpoint string - logsEndpoint string - host string - kernelArgs string - insecure bool - tunnelMode bool - machinesCount int } @@ -212,85 +179,7 @@ func main() { } func init() { - rootCmd.Flags().StringVar(&cfg.wireguardEndpoint, "sidero-link-wireguard-endpoint", "localhost:51821", "advertised Wireguard endpoint") - rootCmd.Flags().StringVar(&cfg.apiEndpoint, "sidero-link-api-endpoint", "https://localhost:8099", "gRPC API endpoint for the SideroLink") - rootCmd.Flags().StringVar(&cfg.joinToken, "sidero-link-join-token", "", "join token") - rootCmd.Flags().StringVar(&cfg.eventsEndpoint, "event-sink-endpoint", "[fdae:41e4:649b:9303::1]:8090", "gRPC API endpoint for the Event Sink") - rootCmd.Flags().StringVar(&cfg.logsEndpoint, "log-receiver-endpoint", "[fdae:41e4:649b:9303::1]:8092", "TCP log receiver endpoint") - rootCmd.Flags().StringVar(&cfg.kernelArgs, "kernel-args", "", "specify the whole configuration using kernel args string") rootCmd.Flags().IntVar(&cfg.machinesCount, "machines", 1, "the number of machines to emulate") } - -type siderolinkEndpoint struct { - apiEndpoint string - joinToken string - insecure bool - tunnelMode bool -} - -// Parse parses the endpoint from string. -func parseSiderolinkEndpoint(sideroLinkParam string) (*siderolinkEndpoint, error) { - urlSchemeMatcher := regexp.MustCompile(`[a-zA-z]+://`) - - if !urlSchemeMatcher.MatchString(sideroLinkParam) { - sideroLinkParam = "grpc://" + sideroLinkParam - } - - u, err := url.Parse(sideroLinkParam) - if err != nil { - return nil, err - } - - result := siderolinkEndpoint{ - apiEndpoint: u.Host, - insecure: u.Scheme == "grpc", - } - - if token := u.Query().Get("jointoken"); token != "" { - result.joinToken = token - } - - if tunnel := u.Query().Get("grpc_tunnel"); tunnel == "true" { - result.tunnelMode = true - } - - if u.Port() == "" && u.Scheme == "https" { - result.apiEndpoint += ":443" - } - - return &result, nil -} - -func parseKernelArgs(kernelArgs string) error { - if cfg.kernelArgs == "" { - return nil - } - - cmdline := procfs.NewCmdline(kernelArgs) - - if s := cmdline.Get(constants.KernelParamEventsSink).Get(0); s != nil { - cfg.eventsEndpoint = *s - } - - if s := cmdline.Get(constants.KernelParamSideroLink).Get(0); s != nil { - endpoint, err := parseSiderolinkEndpoint(*s) - if err != nil { - return err - } - - cfg.apiEndpoint = endpoint.apiEndpoint - cfg.insecure = endpoint.insecure - - if endpoint.joinToken != "" { - cfg.joinToken = endpoint.joinToken - } - } - - if s := cmdline.Get(constants.KernelParamLoggingKernel).Get(0); s != nil { - cfg.logsEndpoint = *s - } - - return nil -} diff --git a/go.mod b/go.mod index b6493a8..f9e1ed3 100644 --- a/go.mod +++ b/go.mod @@ -18,8 +18,9 @@ replace ( ) require ( + github.com/adrg/xdg v0.5.0 github.com/akutz/memconn v0.1.0 - github.com/cosi-project/runtime v0.5.2 + github.com/cosi-project/runtime v0.5.5 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 github.com/hashicorp/go-multierror v1.1.1 github.com/jsimonetti/rtnetlink v1.4.2 @@ -37,6 +38,7 @@ require ( github.com/siderolabs/discovery-api v0.1.4 github.com/siderolabs/discovery-client v0.1.9 github.com/siderolabs/gen v0.5.0 + github.com/siderolabs/go-api-signature v0.3.4 github.com/siderolabs/go-blockdevice v0.4.7 github.com/siderolabs/go-circular v0.1.0 github.com/siderolabs/go-pointer v1.0.0 @@ -45,7 +47,7 @@ require ( github.com/siderolabs/grpc-proxy v0.4.1 github.com/siderolabs/image-factory v0.2.1 github.com/siderolabs/net v0.4.0 - github.com/siderolabs/omni/client v0.39.0 + github.com/siderolabs/omni/client v0.40.0 github.com/siderolabs/siderolink v0.3.9 github.com/siderolabs/talos/pkg/machinery v1.8.0-alpha.1.0.20240709134906-3bab15214de9 github.com/spf13/cobra v1.8.1 @@ -74,12 +76,16 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/GoogleCloudPlatform/k8s-cloud-provider v1.18.1-0.20220218231025-f11817397a1b // indirect github.com/NYTimes/gziphandler v1.1.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/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.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/coreos/go-oidc v2.2.1+incompatible // indirect @@ -138,6 +144,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/runtime-spec v1.2.0 // indirect github.com/opencontainers/selinux v1.11.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pquerna/cachecontrol v0.1.0 // indirect diff --git a/go.sum b/go.sum index eb82329..3f4de63 100644 --- a/go.sum +++ b/go.sum @@ -56,6 +56,15 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5 github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +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/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A= github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -68,10 +77,13 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:W github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/brianvoe/gofakeit/v6 v6.24.0 h1:74yq7RRz/noddscZHRS2T84oHZisW9muwbb8sRnU52A= github.com/brianvoe/gofakeit/v6 v6.24.0/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -84,6 +96,9 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4= github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.9 h1:QFrlgFYf2Qpi8bSpVPK1HBvWpx16v/1TZivyo7pGuBE= +github.com/cloudflare/circl v1.3.9/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -100,8 +115,8 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -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/cosi-project/runtime v0.5.5 h1:GFoHnngpg4QVZluAUDwUbCe/sYOYBXKULxL/6DD99pU= +github.com/cosi-project/runtime v0.5.5/go.mod h1:m+bkfUzKYeUyoqYAQBxdce3bfgncG8BsqcbfKRbvJKs= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= @@ -360,6 +375,8 @@ github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/ github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU= github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +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.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -402,6 +419,8 @@ github.com/siderolabs/discovery-client v0.1.9 h1:yDzvts++Nf/2qczdDUfU5GAibkEIgz/ github.com/siderolabs/discovery-client v0.1.9/go.mod h1:Ew1z07eyJwqNwum84IKYH4S649KEKK5WUmRW49HlXS8= 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.4 h1:bl8qiwhKLVpdzmjzWtKHTvWZyb7Oe4d4qp69imeU6+8= +github.com/siderolabs/go-api-signature v0.3.4/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-circular v0.1.0 h1:zpBJNUbCZSh0odZxA4Dcj0d3ShLLR2WxKW6hTdAtoiE= @@ -418,8 +437,8 @@ github.com/siderolabs/image-factory v0.2.1 h1:JfUSfS17oZnfccUJtjvHyu71Bxe3bVeOqR github.com/siderolabs/image-factory v0.2.1/go.mod h1:Tz/xVU4ZUOgYvqrjaS9wxIzJBSs4vv8P9K9Dw6C/rAo= github.com/siderolabs/net v0.4.0 h1:1bOgVay/ijPkJz4qct98nHsiB/ysLQU0KLoBC4qLm7I= github.com/siderolabs/net v0.4.0/go.mod h1:/ibG+Hm9HU27agp5r9Q3eZicEfjquzNzQNux5uEk0kM= -github.com/siderolabs/omni/client v0.39.0 h1:fkoAghGXQEGZWCZQN1XNgKjuVkhKV+I3BOHp3nR7QVE= -github.com/siderolabs/omni/client v0.39.0/go.mod h1:G/YqsF16E/DwyXQsBCs7MEmN1a+gzPq3aZhoI9iJ2UM= +github.com/siderolabs/omni/client v0.40.0 h1:Y3jiFSu9roGDfUBmVWY7CTTnjHInmDpJwlK9BRR0wkM= +github.com/siderolabs/omni/client v0.40.0/go.mod h1:P3k9FlSkWlx7d5fwLsn2ApOEtgYg3REyJIqD9HiG8Jw= 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/siderolink v0.3.9 h1:lvHFCu+CdfUyMk90g1Zt5r7n1Dw3jhXMxyzXmQ0776o= @@ -532,6 +551,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U 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-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -572,6 +593,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/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.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -612,6 +634,9 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 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/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -644,6 +669,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -700,11 +726,19 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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.21.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/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -717,6 +751,9 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +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.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -779,6 +816,7 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 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.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/hack/compose/docker-compose.yml b/hack/compose/docker-compose.yml index 366e47f..f17718a 100644 --- a/hack/compose/docker-compose.yml +++ b/hack/compose/docker-compose.yml @@ -5,7 +5,6 @@ services: - state:/_out/state container_name: talemu restart: on-failure - network_mode: host cap_add: - NET_ADMIN build: diff --git a/internal/pkg/emu/runtime.go b/internal/pkg/emu/runtime.go index 377a76c..6a7d49a 100644 --- a/internal/pkg/emu/runtime.go +++ b/internal/pkg/emu/runtime.go @@ -51,6 +51,21 @@ func NewRuntime(globalState state.State, kubernetes *kubefactory.Kubernetes, log }, nil } +// State returns runtime state. +func (rt *Runtime) State() state.State { + return rt.globalState +} + +// RegisterQController in the wrapped COSI runtime. +func (rt *Runtime) RegisterQController(ctrl controller.QController) error { + return rt.runtime.RegisterQController(ctrl) +} + +// RegisterController in the wrapped COSI runtime. +func (rt *Runtime) RegisterController(ctrl controller.Controller) error { + return rt.runtime.RegisterController(ctrl) +} + // Run starts the runtime. func (rt *Runtime) Run(ctx context.Context) error { rt.logger.Info("starting global runtime") diff --git a/internal/pkg/kubefactory/etcd.go b/internal/pkg/kubefactory/etcd.go index f7e6574..936508d 100644 --- a/internal/pkg/kubefactory/etcd.go +++ b/internal/pkg/kubefactory/etcd.go @@ -10,6 +10,7 @@ import ( "fmt" "net" "net/url" + "os" "time" "github.com/siderolabs/gen/xslices" @@ -37,6 +38,10 @@ func NewEmbeddedEtcd(ctx context.Context, path string, logger *zap.Logger) (*Etc logger.Info("starting embedded etcd server", zap.String("data_dir", path)) + if err := os.MkdirAll(path, 0o775); err != nil && !os.IsExist(err) { + return nil, err + } + cfg := embed.NewConfig() cfg.Dir = path cfg.EnableGRPCGateway = false diff --git a/internal/pkg/machine/controllers/siderolink.go b/internal/pkg/machine/controllers/siderolink.go index 6d97751..6ea312d 100644 --- a/internal/pkg/machine/controllers/siderolink.go +++ b/internal/pkg/machine/controllers/siderolink.go @@ -40,13 +40,13 @@ import ( // ManagerController interacts with SideroLink API and brings up the SideroLink Wireguard interface. type ManagerController struct { - pd provisionData - nodeKey wgtypes.Key - MachineIndex int + pd provisionData + nodeKey wgtypes.Key + Slot int } func (ctrl *ManagerController) interfaceName() string { - return fmt.Sprintf("%s%d", constants.SideroLinkName, ctrl.MachineIndex) + return fmt.Sprintf("%s%d", constants.SideroLinkName, ctrl.Slot) } // Name implements controller.Controller interface. diff --git a/internal/pkg/machine/events/events.go b/internal/pkg/machine/events/events.go index 3b673ef..6b63400 100644 --- a/internal/pkg/machine/events/events.go +++ b/internal/pkg/machine/events/events.go @@ -10,6 +10,7 @@ import ( "fmt" "net" "net/netip" + "strings" "sync" "time" @@ -43,7 +44,7 @@ type Handler struct { } // NewHandler creates new events handler. -func NewHandler(ctx context.Context, st state.State, machineIndex int) (*Handler, error) { +func NewHandler(ctx context.Context, st state.State) (*Handler, error) { config, err := safe.ReaderGetByID[*runtime.EventSinkConfig](ctx, st, runtime.EventSinkConfigID) if err != nil { return nil, err @@ -54,6 +55,8 @@ func NewHandler(ctx context.Context, st state.State, machineIndex int) (*Handler mu sync.Mutex ) + var linkName string + conn, err := grpc.NewClient( config.TypedSpec().Endpoint, grpc.WithTransportCredentials(insecure.NewCredentials()), @@ -65,28 +68,33 @@ func NewHandler(ctx context.Context, st state.State, machineIndex int) (*Handler var dialer net.Dialer if bindAddress == nil { - var addr *network.NodeAddress + var list safe.List[*network.AddressStatus] - addr, err = safe.ReaderGetByID[*network.NodeAddress](ctx, st, network.NodeAddressDefaultID) + list, err = safe.ReaderListAll[*network.AddressStatus](ctx, st) if err != nil { return nil, err } - if len(addr.TypedSpec().Addresses) == 0 { + addr, ok := list.Find(func(r *network.AddressStatus) bool { + return strings.HasPrefix(r.TypedSpec().LinkName, constants.SideroLinkName) + }) + if !ok { return nil, fmt.Errorf("failed to look up siderolink address") } - siderolinkAddr := addr.TypedSpec().Addresses[0] + siderolinkAddr := addr.TypedSpec().Address bindAddress = net.TCPAddrFromAddrPort(netip.AddrPortFrom( siderolinkAddr.Addr(), 0, )) + + linkName = addr.TypedSpec().LinkName } dialer.LocalAddr = bindAddress - dialer.Control = emunet.BindToInterface(fmt.Sprintf("%s%d", constants.SideroLinkName, machineIndex)) + dialer.Control = emunet.BindToInterface(linkName) return dialer.DialContext(ctx, "tcp", address) }), diff --git a/internal/pkg/machine/machine.go b/internal/pkg/machine/machine.go index 78848ed..30d0cc3 100644 --- a/internal/pkg/machine/machine.go +++ b/internal/pkg/machine/machine.go @@ -40,6 +40,7 @@ type Machine struct { globalState state.State runtime *truntime.Runtime logger *zap.Logger + shutdown chan struct{} uuid string } @@ -49,22 +50,21 @@ func NewMachine(uuid string, logger *zap.Logger, globalState state.State) (*Mach uuid: uuid, logger: logger, globalState: globalState, + shutdown: make(chan struct{}, 1), }, nil } -// SideroLinkParams is the siderolink params needed to join Omni instance. -type SideroLinkParams struct { - APIEndpoint string - JoinToken string - LogsEndpoint string - EventsEndpoint string - Host string - Insecure bool - TunnelMode bool -} - // Run starts the machine. -func (m *Machine) Run(ctx context.Context, siderolinkParams *SideroLinkParams, machineIndex int, kubernetes *kubefactory.Kubernetes) error { +func (m *Machine) Run(ctx context.Context, siderolinkParams *SideroLinkParams, slot int, kubernetes *kubefactory.Kubernetes, options ...Option) error { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + var opts Options + + for _, o := range options { + o(&opts) + } + logSink, err := logging.NewZapCore(siderolinkParams.LogsEndpoint) if err != nil { return err @@ -76,7 +76,7 @@ func (m *Machine) Run(ctx context.Context, siderolinkParams *SideroLinkParams, m m.logger = zap.New(core).With(zap.String("machine", m.uuid)) - rt, err := truntime.NewRuntime(ctx, m.logger, machineIndex, m.uuid, m.globalState, kubernetes, logSink) + rt, err := truntime.NewRuntime(ctx, m.logger, slot, m.uuid, m.globalState, kubernetes, logSink) if err != nil { return err } @@ -161,6 +161,15 @@ func (m *Machine) Run(ctx context.Context, siderolinkParams *SideroLinkParams, m memory, ) + if opts.schematic != "" || opts.talosVersion != "" { + image := talos.NewImage(talos.NamespaceName, talos.ImageID) + + image.TypedSpec().Value.Schematic = opts.schematic + image.TypedSpec().Value.Version = opts.talosVersion + + resources = append(resources, image) + } + for _, r := range resources { if err = rt.State().Create(ctx, r); err != nil { if state.IsConflictError(err) { @@ -171,13 +180,23 @@ func (m *Machine) Run(ctx context.Context, siderolinkParams *SideroLinkParams, m } } - sink, err := events.NewHandler(ctx, rt.State(), machineIndex) + sink, err := events.NewHandler(ctx, rt.State()) if err != nil { return err } var eg errgroup.Group + eg.Go(func() error { + select { + case <-ctx.Done(): + case <-m.shutdown: + cancel() + } + + return nil + }) + eg.Go(func() error { return rt.Run(ctx) }) @@ -195,6 +214,11 @@ func (m *Machine) Cleanup(ctx context.Context) error { return nil } + select { + case m.shutdown <- struct{}{}: + default: + } + // remove all created interfaces links, err := safe.ReaderListAll[*network.LinkSpec](ctx, m.runtime.State()) if err != nil { diff --git a/internal/pkg/machine/options.go b/internal/pkg/machine/options.go new file mode 100644 index 0000000..99dac3e --- /dev/null +++ b/internal/pkg/machine/options.go @@ -0,0 +1,28 @@ +// 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 machine + +// Options is the extra machine options. +type Options struct { + talosVersion string + schematic string +} + +// Option represents a single extra machine option. +type Option func(*Options) + +// WithTalosVersion creates the machine with the initial talos version. +func WithTalosVersion(version string) Option { + return func(o *Options) { + o.talosVersion = version + } +} + +// WithSchematic creates the machine with the initial schematic. +func WithSchematic(schematic string) Option { + return func(o *Options) { + o.schematic = schematic + } +} diff --git a/internal/pkg/machine/runtime/runtime.go b/internal/pkg/machine/runtime/runtime.go index aa0c737..4cc082f 100644 --- a/internal/pkg/machine/runtime/runtime.go +++ b/internal/pkg/machine/runtime/runtime.go @@ -11,7 +11,6 @@ import ( "io" "os" "path/filepath" - "strconv" "github.com/cosi-project/runtime/pkg/controller" "github.com/cosi-project/runtime/pkg/controller/runtime" @@ -36,12 +35,12 @@ type Runtime struct { } // NewRuntime creates new runtime. -func NewRuntime(ctx context.Context, logger *zap.Logger, machineIndex int, id string, globalState state.State, +func NewRuntime(ctx context.Context, logger *zap.Logger, slot int, id string, globalState state.State, kubernetes *kubefactory.Kubernetes, logSink *logging.ZapCore, ) (*Runtime, error) { - stateDir := filepath.Join("_out/state/machines", strconv.FormatInt(int64(machineIndex), 10)) + stateDir := GetStateDir(id) - err := os.MkdirAll(stateDir, 0o664) + err := os.MkdirAll(stateDir, 0o755) if err != nil && !errors.Is(err, os.ErrExist) { return nil, err } @@ -66,7 +65,7 @@ func NewRuntime(ctx context.Context, logger *zap.Logger, machineIndex int, id st controllers := []controller.Controller{ &controllers.ManagerController{ - MachineIndex: machineIndex, + Slot: slot, }, &controllers.LinkSpecController{}, &controllers.LinkStatusController{}, diff --git a/internal/pkg/machine/runtime/state.go b/internal/pkg/machine/runtime/state.go index 7b9aad8..c28d2f7 100644 --- a/internal/pkg/machine/runtime/state.go +++ b/internal/pkg/machine/runtime/state.go @@ -17,22 +17,37 @@ import ( "github.com/cosi-project/runtime/pkg/state/impl/namespaced" "github.com/cosi-project/runtime/pkg/state/impl/store" "github.com/cosi-project/runtime/pkg/state/impl/store/bolt" + "github.com/siderolabs/gen/xslices" "go.etcd.io/bbolt" "go.uber.org/zap" ) +// NamespacedState defines additional namespaced state to pass to the namespaced.NewState. +type NamespacedState struct { + State state.State + Namespace string +} + // NewState creates State. -func NewState(path string, logger *zap.Logger) (state.State, io.Closer, error) { +func NewState(path string, logger *zap.Logger, namespacedStates ...NamespacedState) (state.State, io.Closer, error) { builder, backingStore, err := newBoltStateBuilder(path, &bbolt.Options{}, true, logger) if err != nil { return nil, nil, err } + nsStates := xslices.ToMap(namespacedStates, func(nst NamespacedState) (string, state.State) { + return nst.Namespace, nst.State + }) + state := state.WrapCore(namespaced.NewState(func(n resource.Namespace) state.CoreState { if n == meta.NamespaceName { return inmem.NewState(n) } + if st, ok := nsStates[n]; ok { + return st + } + return builder(n) })) @@ -117,3 +132,8 @@ func newBoltStateBuilder(path string, options *bbolt.Options, compact bool, logg )(ns) }, boltStore, nil } + +// GetStateDir constructs state directory for the machine. +func GetStateDir(id string) string { + return filepath.Join("_out/state/machines", id) +} diff --git a/internal/pkg/machine/siderolink.go b/internal/pkg/machine/siderolink.go new file mode 100644 index 0000000..26b62f2 --- /dev/null +++ b/internal/pkg/machine/siderolink.go @@ -0,0 +1,111 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package machine + +import ( + "net/url" + "regexp" + + "github.com/siderolabs/go-procfs/procfs" + "github.com/siderolabs/talos/pkg/machinery/constants" +) + +// SideroLinkParams is the siderolink params needed to join Omni instance. +type SideroLinkParams struct { + APIEndpoint string + JoinToken string + LogsEndpoint string + EventsEndpoint string + Host string + Insecure bool + TunnelMode bool +} + +// ParseKernelArgs string into siderolink params. +func ParseKernelArgs(kernelArgs string) (*SideroLinkParams, error) { + cmdline := procfs.NewCmdline(kernelArgs) + + var ( + eventsEndpoint string + apiEndpoint string + joinToken string + logsEndpoint string + insecure bool + tunnelMode bool + ) + + if s := cmdline.Get(constants.KernelParamEventsSink).Get(0); s != nil { + eventsEndpoint = *s + } + + if s := cmdline.Get(constants.KernelParamSideroLink).Get(0); s != nil { + endpoint, err := parseSiderolinkEndpoint(*s) + if err != nil { + return nil, err + } + + apiEndpoint = endpoint.apiEndpoint + insecure = endpoint.insecure + tunnelMode = endpoint.tunnelMode + + if endpoint.joinToken != "" { + joinToken = endpoint.joinToken + } + } + + if s := cmdline.Get(constants.KernelParamLoggingKernel).Get(0); s != nil { + logsEndpoint = *s + } + + return &SideroLinkParams{ + Host: apiEndpoint, + APIEndpoint: apiEndpoint, + JoinToken: joinToken, + Insecure: insecure, + EventsEndpoint: eventsEndpoint, + LogsEndpoint: logsEndpoint, + TunnelMode: tunnelMode, + }, nil +} + +type siderolinkEndpoint struct { + apiEndpoint string + joinToken string + insecure bool + tunnelMode bool +} + +// Parse parses the endpoint from string. +func parseSiderolinkEndpoint(sideroLinkParam string) (*siderolinkEndpoint, error) { + urlSchemeMatcher := regexp.MustCompile(`[a-zA-z]+://`) + + if !urlSchemeMatcher.MatchString(sideroLinkParam) { + sideroLinkParam = "grpc://" + sideroLinkParam + } + + u, err := url.Parse(sideroLinkParam) + if err != nil { + return nil, err + } + + result := siderolinkEndpoint{ + apiEndpoint: u.Host, + insecure: u.Scheme == "grpc", + } + + if token := u.Query().Get("jointoken"); token != "" { + result.joinToken = token + } + + if tunnel := u.Query().Get("grpc_tunnel"); tunnel == "true" { + result.tunnelMode = true + } + + if u.Port() == "" && u.Scheme == "https" { + result.apiEndpoint += ":443" + } + + return &result, nil +} diff --git a/internal/pkg/provider/clientconfig/clientconfig.go b/internal/pkg/provider/clientconfig/clientconfig.go new file mode 100644 index 0000000..ab5bade --- /dev/null +++ b/internal/pkg/provider/clientconfig/clientconfig.go @@ -0,0 +1,186 @@ +// 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 clientconfig holds the configuration for the test client for Omni API. +package clientconfig + +import ( + "context" + "fmt" + "os" + "path/filepath" + "sync" + "time" + + "github.com/adrg/xdg" + "github.com/hashicorp/go-multierror" + "github.com/siderolabs/gen/containers" + authpb "github.com/siderolabs/go-api-signature/api/auth" + authcli "github.com/siderolabs/go-api-signature/pkg/client/auth" + "github.com/siderolabs/go-api-signature/pkg/client/interceptor" + "github.com/siderolabs/go-api-signature/pkg/message" + "github.com/siderolabs/go-api-signature/pkg/pgp" + "github.com/siderolabs/omni/client/pkg/client" + "google.golang.org/grpc" +) + +const ( + defaultEmail = "test-user@siderolabs.com" +) + +type clientCacheKey struct { + role string + email string + skipUserRole bool +} + +type clientOrError struct { + client *client.Client + err error +} + +var clientCache = containers.ConcurrentMap[clientCacheKey, clientOrError]{} + +// ClientConfig is a test client. +type ClientConfig struct { + endpoint string +} + +// New creates a new test client config. +func New(endpoint string) *ClientConfig { + return &ClientConfig{ + endpoint: endpoint, + } +} + +// GetClient returns a test client for the default test email. +// +// Clients are cached by their configuration, so if a client with the +// given configuration was created before, the cached one will be returned. +func (t *ClientConfig) GetClient(publicKeyOpts ...authcli.RegisterPGPPublicKeyOption) (*client.Client, error) { + return t.GetClientForEmail(defaultEmail, publicKeyOpts...) +} + +// GetClientForEmail returns a test client for the given email. +// +// Clients are cached by their configuration, so if a client with the +// given configuration was created before, the cached one will be returned. +func (t *ClientConfig) GetClientForEmail(email string, publicKeyOpts ...authcli.RegisterPGPPublicKeyOption) (*client.Client, error) { + cacheKey := t.buildCacheKey(email, publicKeyOpts) + + cliOrErr, _ := clientCache.GetOrCall(cacheKey, func() clientOrError { + signatureInterceptor := buildSignatureInterceptor(email, publicKeyOpts...) + + cli, err := client.New(t.endpoint, + client.WithGrpcOpts( + grpc.WithUnaryInterceptor(signatureInterceptor.Unary()), + grpc.WithStreamInterceptor(signatureInterceptor.Stream()), + ), + ) + + return clientOrError{ + client: cli, + err: err, + } + }) + + return cliOrErr.client, cliOrErr.err +} + +// Close closes all the clients created by this config. +func (t *ClientConfig) Close() error { + var multiErr error + + clientCache.ForEach(func(_ clientCacheKey, cliOrErr clientOrError) { + if cliOrErr.client != nil { + if err := cliOrErr.client.Close(); err != nil { + multiErr = multierror.Append(multiErr, err) + } + } + }) + + return multiErr +} + +func (t *ClientConfig) buildCacheKey(email string, publicKeyOpts []authcli.RegisterPGPPublicKeyOption) clientCacheKey { + var req authpb.RegisterPublicKeyRequest + + for _, o := range publicKeyOpts { + o(&req) + } + + return clientCacheKey{ + role: req.Role, + email: email, + skipUserRole: req.SkipUserRole, + } +} + +var talosAPIKeyMutex sync.Mutex + +// TalosAPIKeyPrepare prepares a public key to be used with tests interacting via Talos API client using the default test email. +func TalosAPIKeyPrepare(ctx context.Context, client *client.Client, contextName string) error { + return TalosAPIKeyPrepareWithEmail(ctx, client, contextName, defaultEmail) +} + +// TalosAPIKeyPrepareWithEmail prepares a public key to be used with tests interacting via Talos API client using the given email. +func TalosAPIKeyPrepareWithEmail(ctx context.Context, client *client.Client, contextName, email string) error { + talosAPIKeyMutex.Lock() + defer talosAPIKeyMutex.Unlock() + + path, err := xdg.DataFile(filepath.Join("talos", "keys", fmt.Sprintf("%s-%s.pgp", contextName, email))) + if err != nil { + return err + } + + stat, err := os.Stat(path) + if err != nil && !os.IsNotExist(err) { + return err + } + + if stat != nil && time.Since(stat.ModTime()) < 2*time.Hour { + return nil + } + + newKey, err := pgp.GenerateKey("", "", email, 4*time.Hour) + if err != nil { + return err + } + + err = registerKey(ctx, client.Auth(), newKey, email) + if err != nil { + return err + } + + keyArmored, err := newKey.Armor() + if err != nil { + return err + } + + return os.WriteFile(path, []byte(keyArmored), 0o600) +} + +func buildSignatureInterceptor(email string, publicKeyOpts ...authcli.RegisterPGPPublicKeyOption) *interceptor.Interceptor { + userKeyFunc := func(ctx context.Context, cc *grpc.ClientConn, _ *interceptor.Options) (message.Signer, error) { + newKey, err := pgp.GenerateKey("", "", email, 4*time.Hour) + if err != nil { + return nil, err + } + + authCli := authcli.NewClient(cc) + + err = registerKey(ctx, authCli, newKey, email, publicKeyOpts...) + if err != nil { + return nil, err + } + + return newKey, nil + } + + return interceptor.New(interceptor.Options{ + GetUserKeyFunc: userKeyFunc, + RenewUserKeyFunc: userKeyFunc, + Identity: email, + }) +} diff --git a/internal/pkg/provider/clientconfig/register_key.go b/internal/pkg/provider/clientconfig/register_key.go new file mode 100644 index 0000000..6508ac5 --- /dev/null +++ b/internal/pkg/provider/clientconfig/register_key.go @@ -0,0 +1,38 @@ +// 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 clientconfig + +import ( + "context" + "time" + + "github.com/siderolabs/go-api-signature/pkg/client/auth" + "github.com/siderolabs/go-api-signature/pkg/pgp" + "google.golang.org/grpc/metadata" +) + +func registerKey(ctx context.Context, cli *auth.Client, key *pgp.Key, email string, opts ...auth.RegisterPGPPublicKeyOption) error { + armoredPublicKey, err := key.ArmorPublic() + if err != nil { + return err + } + + _, err = cli.RegisterPGPPublicKey(ctx, email, []byte(armoredPublicKey), opts...) + if err != nil { + return err + } + + debugCtx := metadata.AppendToOutgoingContext(ctx, "x-sidero-debug-verified-email", email) + + err = cli.ConfirmPublicKey(debugCtx, key.Fingerprint()) + if err != nil { + return err + } + + timeoutCtx, timeoutCtxCancel := context.WithTimeout(ctx, 10*time.Second) + defer timeoutCtxCancel() + + return cli.AwaitPublicKeyConfirmation(timeoutCtx, key.Fingerprint()) +} diff --git a/internal/pkg/provider/controllers/machine.go b/internal/pkg/provider/controllers/machine.go new file mode 100644 index 0000000..4b84377 --- /dev/null +++ b/internal/pkg/provider/controllers/machine.go @@ -0,0 +1,214 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package controllers provides the controller for the machine request status. +package controllers + +import ( + "context" + "errors" + "fmt" + "os" + + "github.com/cosi-project/runtime/pkg/controller" + "github.com/cosi-project/runtime/pkg/resource" + "github.com/cosi-project/runtime/pkg/safe" + "github.com/cosi-project/runtime/pkg/state" + "github.com/cosi-project/runtime/pkg/task" + cloudspecs "github.com/siderolabs/omni/client/api/omni/specs/cloud" + omnires "github.com/siderolabs/omni/client/pkg/omni/resources" + "github.com/siderolabs/omni/client/pkg/omni/resources/cloud" + "github.com/siderolabs/omni/client/pkg/omni/resources/siderolink" + "go.uber.org/zap" + + "github.com/siderolabs/talemu/internal/pkg/kubefactory" + "github.com/siderolabs/talemu/internal/pkg/machine" + "github.com/siderolabs/talemu/internal/pkg/machine/runtime" + "github.com/siderolabs/talemu/internal/pkg/machine/runtime/resources/emu" + machinetask "github.com/siderolabs/talemu/internal/pkg/provider/controllers/machine" + "github.com/siderolabs/talemu/internal/pkg/provider/resources" +) + +// MachineController runs a machine for each machine request. +type MachineController struct { + runner *task.Runner[any, machinetask.TaskSpec] + kubernetes *kubefactory.Kubernetes + globalState state.State +} + +// NewMachineController creates new machine controller. +func NewMachineController(globalState state.State, kubernetes *kubefactory.Kubernetes) *MachineController { + return &MachineController{ + runner: task.NewEqualRunner[machinetask.TaskSpec](), + globalState: globalState, + kubernetes: kubernetes, + } +} + +// Name implements controller.Controller interface. +func (ctrl *MachineController) Name() string { + return "provider.MachineController" +} + +// Inputs implements controller.Controller interface. +func (ctrl *MachineController) Inputs() []controller.Input { + return []controller.Input{ + { + Namespace: emu.NamespaceName, + Type: resources.MachineType, + Kind: controller.InputStrong, + }, + { + Namespace: omnires.CloudProviderNamespace, + Type: cloud.MachineRequestStatusType, + Kind: controller.InputDestroyReady, + }, + { + Namespace: omnires.DefaultNamespace, + Type: siderolink.ConnectionParamsType, + Kind: controller.InputWeak, + }, + } +} + +// Outputs implements controller.Controller interface. +func (ctrl *MachineController) Outputs() []controller.Output { + return []controller.Output{ + { + Type: cloud.MachineRequestStatusType, + Kind: controller.OutputShared, + }, + } +} + +// Run implements controller.Controller interface. +// +//nolint:gocognit,gocyclo,cyclop +func (ctrl *MachineController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error { + for { + select { + case <-ctx.Done(): + return nil + case <-r.EventCh(): + } + + machines, err := safe.ReaderListAll[*resources.Machine](ctx, r) + if err != nil { + return errors.New("error listing machines") + } + + touchedIDs := make(map[resource.ID]struct{}) + + for it := machines.Iterator(); it.Next(); { + m := it.Value() + + if m.Metadata().Phase() == resource.PhaseTearingDown { + continue + } + + if !m.Metadata().Finalizers().Has(ctrl.Name()) { + if err = r.AddFinalizer(ctx, m.Metadata(), ctrl.Name()); err != nil { + return err + } + } + + if err = safe.WriterModify(ctx, r, cloud.NewMachineRequestStatus(m.Metadata().ID()), func(r *cloud.MachineRequestStatus) error { + r.TypedSpec().Value.Id = m.TypedSpec().Value.Uuid + r.TypedSpec().Value.Stage = cloudspecs.MachineRequestStatusSpec_PROVISIONED + + return nil + }); err != nil { + if state.IsPhaseConflictError(err) { + continue + } + + return err + } + + var ( + connectionParams *siderolink.ConnectionParams + params *machine.SideroLinkParams + ) + + connectionParams, err = safe.ReaderGetByID[*siderolink.ConnectionParams](ctx, r, siderolink.ConfigID) + if err != nil { + return err + } + + params, err = machine.ParseKernelArgs(connectionParams.TypedSpec().Value.Args) + if err != nil { + return err + } + + ctrl.runner.StartTask(ctx, logger, m.Metadata().ID(), machinetask.TaskSpec{ + Machine: m, + GlobalState: ctrl.globalState, + Kubernetes: ctrl.kubernetes, + Params: params, + }, nil) + + touchedIDs[m.Metadata().ID()] = struct{}{} + } + + machineRequestStatuses, err := safe.ReaderListAll[*cloud.MachineRequestStatus](ctx, r) + if err != nil { + return fmt.Errorf("error listing resources: %w", err) + } + + for it := machineRequestStatuses.Iterator(); it.Next(); { + res := it.Value() + + machine := resources.NewMachine(emu.NamespaceName, res.Metadata().ID()) + + machine.TypedSpec().Value.Uuid = res.TypedSpec().Value.Id + + if _, ok := touchedIDs[res.Metadata().ID()]; !ok { + var ready bool + + ready, err = r.Teardown(ctx, res.Metadata()) + if err != nil { + return err + } + + if !ready { + continue + } + + if err = ctrl.resetMachine(ctx, machine, logger); err != nil { + return err + } + + if err = r.RemoveFinalizer(ctx, machine.Metadata(), ctrl.Name()); err != nil { + return err + } + + if err = r.Destroy(ctx, res.Metadata()); err != nil && !state.IsNotFoundError(err) { + return err + } + } + } + + r.ResetRestartBackoff() + } +} + +func (ctrl *MachineController) resetMachine(ctx context.Context, m *resources.Machine, logger *zap.Logger) error { + logger.Info("reset machine", zap.String("uuid", m.TypedSpec().Value.Uuid), zap.String("request", m.Metadata().ID())) + + ctrl.runner.StopTask(logger, m.Metadata().ID()) + + stateDir := runtime.GetStateDir(m.TypedSpec().Value.Uuid) + + err := os.RemoveAll(stateDir) + if !os.IsNotExist(err) { + return err + } + + err = ctrl.globalState.Destroy(ctx, emu.NewMachineStatus(emu.NamespaceName, m.TypedSpec().Value.Uuid).Metadata()) + if err != nil && !state.IsNotFoundError(err) { + return err + } + + return nil +} diff --git a/internal/pkg/provider/controllers/machine/machine.go b/internal/pkg/provider/controllers/machine/machine.go new file mode 100644 index 0000000..f66071a --- /dev/null +++ b/internal/pkg/provider/controllers/machine/machine.go @@ -0,0 +1,57 @@ +// 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 machine implements fake machine runner. +package machine + +import ( + "context" + + "github.com/cosi-project/runtime/pkg/state" + "github.com/cosi-project/runtime/pkg/task" + "go.uber.org/zap" + + "github.com/siderolabs/talemu/internal/pkg/kubefactory" + "github.com/siderolabs/talemu/internal/pkg/machine" + "github.com/siderolabs/talemu/internal/pkg/provider/resources" +) + +// TaskSpec runs fake machine. +type TaskSpec struct { + _ [0]func() // make uncomparable + + Machine *resources.Machine + GlobalState state.State + Params *machine.SideroLinkParams + Kubernetes *kubefactory.Kubernetes +} + +// ID implements task.TaskSpec. +func (s TaskSpec) ID() task.ID { + return s.Machine.Metadata().ID() +} + +// Equal implements task.TaskSpec. +func (s TaskSpec) Equal(other TaskSpec) bool { + return s.Machine.Metadata().Equal(*other.Machine.Metadata()) && s.Machine.TypedSpec().Value.EqualVT(other.Machine.TypedSpec().Value) +} + +// RunTask implements task.TaskSpec. +func (s TaskSpec) RunTask(ctx context.Context, logger *zap.Logger, _ any) error { + m, err := machine.NewMachine(s.Machine.TypedSpec().Value.Uuid, logger, s.GlobalState) + if err != nil { + return err + } + + defer m.Cleanup(ctx) //nolint:errcheck + + return m.Run( + ctx, + s.Params, + int(s.Machine.TypedSpec().Value.Slot), + s.Kubernetes, + machine.WithTalosVersion(s.Machine.TypedSpec().Value.TalosVersion), + machine.WithSchematic(s.Machine.TypedSpec().Value.Schematic), + ) +} diff --git a/internal/pkg/provider/controllers/machine_request_status.go b/internal/pkg/provider/controllers/machine_request_status.go new file mode 100644 index 0000000..9b09635 --- /dev/null +++ b/internal/pkg/provider/controllers/machine_request_status.go @@ -0,0 +1,100 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package controllers provides the controllers for the machine requests handling. +package controllers + +import ( + "context" + "fmt" + + "github.com/cosi-project/runtime/pkg/controller" + "github.com/cosi-project/runtime/pkg/controller/generic/qtransform" + "github.com/cosi-project/runtime/pkg/safe" + "github.com/siderolabs/gen/optional" + "github.com/siderolabs/omni/client/pkg/omni/resources/cloud" + "github.com/siderolabs/omni/client/pkg/omni/resources/omni" + "go.uber.org/zap" + + "github.com/siderolabs/talemu/internal/pkg/machine/runtime/resources/emu" + "github.com/siderolabs/talemu/internal/pkg/provider/meta" + "github.com/siderolabs/talemu/internal/pkg/provider/resources" +) + +const maxMachineCount = 10000 + +// MachineRequestStatusController creates the system config patch that contains the maintenance config. +type MachineRequestStatusController = qtransform.QController[*cloud.MachineRequest, *resources.Machine] + +// NewMachineRequestStatusController initializes MachineRequestStatusController. +func NewMachineRequestStatusController() *MachineRequestStatusController { + return qtransform.NewQController( + qtransform.Settings[*cloud.MachineRequest, *resources.Machine]{ + Name: "provider.MachineRequestStatusController", + MapMetadataOptionalFunc: func(request *cloud.MachineRequest) optional.Optional[*resources.Machine] { + providerID, ok := request.Metadata().Labels().Get(omni.LabelCloudProviderID) + if ok && providerID == meta.ProviderID { + return optional.Some(resources.NewMachine(emu.NamespaceName, request.Metadata().ID())) + } + + return optional.None[*resources.Machine]() + }, + UnmapMetadataFunc: func(m *resources.Machine) *cloud.MachineRequest { + return cloud.NewMachineRequest(m.Metadata().ID()) + }, + TransformExtraOutputFunc: func(ctx context.Context, r controller.ReaderWriter, logger *zap.Logger, request *cloud.MachineRequest, machine *resources.Machine) error { + machine.Metadata().Labels().Set(omni.MachineLabelsType, 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)) + + var err error + + if machine.TypedSpec().Value.Slot == 0 { + machine.TypedSpec().Value.Slot, err = pickFreeSlot(ctx, r) + if err != nil { + return err + } + + logger.Info("requested machine", + zap.Int32("slot", machine.TypedSpec().Value.Slot), + zap.String("uuid", machine.TypedSpec().Value.Uuid), + ) + } + + uuid := fmt.Sprintf("%05d803-c798-4da7-a410-f09abb48c8d8", machine.TypedSpec().Value.Slot) + + machine.TypedSpec().Value.Schematic = schematicID + machine.TypedSpec().Value.TalosVersion = talosVersion + machine.TypedSpec().Value.Uuid = uuid + + return nil + }, + }, + ) +} + +func pickFreeSlot(ctx context.Context, r controller.Reader) (int32, error) { + list, err := safe.ReaderListAll[*resources.Machine](ctx, r) + if err != nil { + return 0, err + } + + used := make(map[int32]struct{}, list.Len()) + + list.ForEach(func(r *resources.Machine) { + used[r.TypedSpec().Value.Slot] = struct{}{} + }) + + for i := int32(1); i < maxMachineCount; i++ { + _, inUse := used[i] + if !inUse { + return i, nil + } + } + + return 0, fmt.Errorf("no free machine slots") +} diff --git a/internal/pkg/provider/meta/meta.go b/internal/pkg/provider/meta/meta.go new file mode 100644 index 0000000..0cd73f2 --- /dev/null +++ b/internal/pkg/provider/meta/meta.go @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package meta contains meta information about the provider. +package meta + +// ProviderID is the ID of the provider. +var ProviderID = "talemu" diff --git a/internal/pkg/provider/provider.go b/internal/pkg/provider/provider.go new file mode 100644 index 0000000..3f6d1ed --- /dev/null +++ b/internal/pkg/provider/provider.go @@ -0,0 +1,39 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package provider implements emulator cloud provider for Omni. +package provider + +import ( + "github.com/cosi-project/runtime/pkg/controller" + + "github.com/siderolabs/talemu/internal/pkg/emu" + "github.com/siderolabs/talemu/internal/pkg/kubefactory" + "github.com/siderolabs/talemu/internal/pkg/provider/controllers" +) + +// RegisterControllers registers additional controllers required for the cloud provider. +func RegisterControllers(runtime *emu.Runtime, kubernetes *kubefactory.Kubernetes) error { + qcontrollers := []controller.QController{ + controllers.NewMachineRequestStatusController(), + } + + controllers := []controller.Controller{ + controllers.NewMachineController(runtime.State(), kubernetes), + } + + for _, ctrl := range qcontrollers { + if err := runtime.RegisterQController(ctrl); err != nil { + return err + } + } + + for _, ctrl := range controllers { + if err := runtime.RegisterController(ctrl); err != nil { + return err + } + } + + return nil +} diff --git a/internal/pkg/provider/resources/machine.go b/internal/pkg/provider/resources/machine.go new file mode 100644 index 0000000..5a39be8 --- /dev/null +++ b/internal/pkg/provider/resources/machine.go @@ -0,0 +1,47 @@ +// 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/talemu/api/specs" + "github.com/siderolabs/talemu/internal/pkg/machine/runtime/resources/emu" +) + +// NewMachine creates new Machine. +func NewMachine(ns, id string) *Machine { + return typed.NewResource[MachineSpec, MachineExtension]( + resource.NewMetadata(ns, MachineType, id, resource.VersionUndefined), + protobuf.NewResourceSpec(&specs.MachineSpec{}), + ) +} + +// MachineType is the type of Machine resource. +// +// tsgen:MachineType +const MachineType = resource.Type("Machines.talemu.sidero.dev") + +// Machine describes virtual machine configuration. +type Machine = typed.Resource[MachineSpec, MachineExtension] + +// MachineSpec wraps specs.MachineSpec. +type MachineSpec = protobuf.ResourceSpec[specs.MachineSpec, *specs.MachineSpec] + +// MachineExtension providers auxiliary methods for Machine resource. +type MachineExtension struct{} + +// ResourceDefinition implements [typed.Extension] interface. +func (MachineExtension) ResourceDefinition() meta.ResourceDefinitionSpec { + return meta.ResourceDefinitionSpec{ + Type: MachineType, + Aliases: []resource.Type{}, + DefaultNamespace: emu.NamespaceName, + PrintColumns: []meta.PrintColumn{}, + } +} diff --git a/internal/pkg/provider/resources/resources.go b/internal/pkg/provider/resources/resources.go new file mode 100644 index 0000000..135e4dd --- /dev/null +++ b/internal/pkg/provider/resources/resources.go @@ -0,0 +1,40 @@ +// 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 cloud provider resources. +package resources + +import ( + "fmt" + + "github.com/cosi-project/runtime/pkg/controller/generic" + "github.com/cosi-project/runtime/pkg/resource" + "github.com/cosi-project/runtime/pkg/resource/meta" + "github.com/cosi-project/runtime/pkg/resource/protobuf" +) + +// NamespaceName sets the default namespace name for the emulator resources. +const NamespaceName = "emulator" + +func init() { + mustRegisterResource(MachineType, &Machine{}) +} + +var resources []generic.ResourceWithRD + +// mustRegisterResource adds resource to the registry, registers it's protobuf decoders/encoders. +func mustRegisterResource[T any, R interface { + protobuf.Res[T] + meta.ResourceDefinitionProvider +}]( + resourceType resource.Type, + r R, +) { + resources = append(resources, r) + + err := protobuf.RegisterResource(resourceType, r) + if err != nil { + panic(fmt.Errorf("failed to register resource %T: %w", r, err)) + } +} diff --git a/internal/pkg/provider/state.go b/internal/pkg/provider/state.go new file mode 100644 index 0000000..c8e91d5 --- /dev/null +++ b/internal/pkg/provider/state.go @@ -0,0 +1,41 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package provider + +import ( + "github.com/cosi-project/runtime/pkg/state" + "github.com/siderolabs/omni/client/pkg/client" +) + +// State creates new cloud provider state. +type State struct { + Client *client.Client +} + +// NewState creates new cloud provider state. +func NewState(endpoint, key string) (*State, error) { + opts := []client.Option{client.WithInsecureSkipTLSVerify(true)} + + if key != "" { + opts = append(opts, client.WithServiceAccount(key)) + } + + client, err := client.New(endpoint, opts...) + if err != nil { + return nil, err + } + + return &State{Client: client}, nil +} + +// Close closes the connection to Omni. +func (s *State) Close() error { + return s.Client.Close() +} + +// State returns COSI state. +func (s *State) State() state.State { + return s.Client.Omni().State() +}