From 0ec35764ee28e471d7988e8192fb001396711187 Mon Sep 17 00:00:00 2001
From: Shiming Zhang
Date: Wed, 31 Jul 2024 17:58:20 +0800
Subject: [PATCH] Add kwok operator
---
Makefile | 31 +-
charts/operator/.helmignore | 1 +
charts/operator/Chart.yaml | 17 +
charts/operator/README.md | 49 +++
charts/operator/README.md.gotmpl | 9 +
charts/operator/crds | 1 +
charts/operator/templates/_helpers.tpl | 62 +++
charts/operator/templates/deployment.yaml | 46 +++
charts/operator/templates/role.yaml | 47 +++
charts/operator/templates/role_binding.yaml | 12 +
.../operator/templates/service_account.yaml | 5 +
charts/operator/values.yaml | 26 ++
cmd/kwok-operator/main.go | 53 +++
hack/releases-helm-charts.sh | 1 +
hack/update-codegen.sh | 3 +
hack/update-controllergen.sh | 11 +
hack/update-helm-charts.sh | 4 +
images/operator/Dockerfile | 20 +
images/operator/build.sh | 217 +++++++++++
kustomize/operator/controller.yaml | 6 +
.../operator.kwok.x-k8s.io_controllers.yaml | 128 ++++++
kustomize/operator/crd/kustomization.yaml | 4 +
kustomize/operator/deployment.yaml | 14 +
kustomize/operator/kustomization.yaml | 14 +
kustomize/operator/rbac/kustomization.yaml | 6 +
kustomize/operator/rbac/role.yaml | 47 +++
kustomize/operator/rbac/role_binding.yaml | 12 +
kustomize/operator/rbac/service_account.yaml | 5 +
pkg/apis/internalversion/controller_types.go | 49 +++
pkg/apis/internalversion/doc.go | 1 +
.../zz_generated.conversion.go | 105 +++++
.../internalversion/zz_generated.deepcopy.go | 67 ++++
pkg/apis/operator/v1alpha1/condition.go | 70 ++++
.../operator/v1alpha1/controller_types.go | 89 +++++
pkg/apis/operator/v1alpha1/doc.go | 26 ++
.../operator/v1alpha1/groupversion_info.go | 39 ++
pkg/apis/operator/v1alpha1/register.go | 29 ++
.../v1alpha1/zz_generated.deepcopy.go | 176 +++++++++
.../v1alpha1/zz_generated.defaults.go | 51 +++
pkg/client/clientset/versioned/clientset.go | 15 +-
.../versioned/fake/clientset_generated.go | 7 +
.../clientset/versioned/fake/register.go | 2 +
.../clientset/versioned/scheme/register.go | 2 +
.../typed/operator/v1alpha1/controller.go | 69 ++++
.../versioned/typed/operator/v1alpha1/doc.go | 20 +
.../typed/operator/v1alpha1/fake/doc.go | 20 +
.../operator/v1alpha1/fake/fake_controller.go | 147 +++++++
.../v1alpha1/fake/fake_operator_client.go | 40 ++
.../operator/v1alpha1/generated_expansion.go | 21 +
.../operator/v1alpha1/operator_client.go | 107 +++++
pkg/operator/cmd/root.go | 125 ++++++
pkg/operator/controllers/controller.go | 202 ++++++++++
pkg/operator/controllers/doc.go | 18 +
site/content/en/docs/generated/apis.md | 364 ++++++++++++++++++
.../build-with-push-bucket-staging.txt | 2 +
.../testdata/build-with-push-bucket.txt | 2 +
.../testdata/build-with-push-ghrelease.txt | 3 +
test/release/testdata/build.txt | 1 +
.../cross-build-with-push-bucket-staging.txt | 12 +
.../testdata/cross-build-with-push-bucket.txt | 12 +
.../cross-build-with-push-ghrelease.txt | 18 +
test/release/testdata/cross-build.txt | 6 +
62 files changed, 2765 insertions(+), 3 deletions(-)
create mode 100644 charts/operator/.helmignore
create mode 100644 charts/operator/Chart.yaml
create mode 100644 charts/operator/README.md
create mode 100644 charts/operator/README.md.gotmpl
create mode 120000 charts/operator/crds
create mode 100644 charts/operator/templates/_helpers.tpl
create mode 100644 charts/operator/templates/deployment.yaml
create mode 100644 charts/operator/templates/role.yaml
create mode 100644 charts/operator/templates/role_binding.yaml
create mode 100644 charts/operator/templates/service_account.yaml
create mode 100644 charts/operator/values.yaml
create mode 100644 cmd/kwok-operator/main.go
create mode 100644 images/operator/Dockerfile
create mode 100755 images/operator/build.sh
create mode 100644 kustomize/operator/controller.yaml
create mode 100644 kustomize/operator/crd/bases/operator.kwok.x-k8s.io_controllers.yaml
create mode 100644 kustomize/operator/crd/kustomization.yaml
create mode 100644 kustomize/operator/deployment.yaml
create mode 100644 kustomize/operator/kustomization.yaml
create mode 100644 kustomize/operator/rbac/kustomization.yaml
create mode 100644 kustomize/operator/rbac/role.yaml
create mode 100644 kustomize/operator/rbac/role_binding.yaml
create mode 100644 kustomize/operator/rbac/service_account.yaml
create mode 100644 pkg/apis/internalversion/controller_types.go
create mode 100644 pkg/apis/operator/v1alpha1/condition.go
create mode 100644 pkg/apis/operator/v1alpha1/controller_types.go
create mode 100644 pkg/apis/operator/v1alpha1/doc.go
create mode 100644 pkg/apis/operator/v1alpha1/groupversion_info.go
create mode 100644 pkg/apis/operator/v1alpha1/register.go
create mode 100644 pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go
create mode 100644 pkg/apis/operator/v1alpha1/zz_generated.defaults.go
create mode 100644 pkg/client/clientset/versioned/typed/operator/v1alpha1/controller.go
create mode 100644 pkg/client/clientset/versioned/typed/operator/v1alpha1/doc.go
create mode 100644 pkg/client/clientset/versioned/typed/operator/v1alpha1/fake/doc.go
create mode 100644 pkg/client/clientset/versioned/typed/operator/v1alpha1/fake/fake_controller.go
create mode 100644 pkg/client/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go
create mode 100644 pkg/client/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go
create mode 100644 pkg/client/clientset/versioned/typed/operator/v1alpha1/operator_client.go
create mode 100644 pkg/operator/cmd/root.go
create mode 100644 pkg/operator/controllers/controller.go
create mode 100644 pkg/operator/controllers/doc.go
diff --git a/Makefile b/Makefile
index 6bbd7faacf..cc7e900d73 100644
--- a/Makefile
+++ b/Makefile
@@ -39,7 +39,7 @@ KUBE_RELEASES ?= $(shell echo $(SUPPORTED_KUBE_RELEASES) | cut -d ' ' -f 1-$(NUM
# The latest kube release
LATEST_KUBE_RELEASE ?= $(shell echo $(SUPPORTED_KUBE_RELEASES) | cut -d ' ' -f 1)
-BINARY ?= kwok kwokctl
+BINARY ?= kwok kwokctl kwok-operator
IMAGE_PREFIX ?=
@@ -64,9 +64,11 @@ PRE_RELEASE ?=
ifeq ($(STAGING_IMAGE_PREFIX),)
KWOK_IMAGE ?= kwok
+KWOK_OPERATOR_IMAGE ?= operator
CLUSTER_IMAGE ?= cluster
else
KWOK_IMAGE ?= $(STAGING_IMAGE_PREFIX)/kwok
+KWOK_OPERATOR_IMAGE ?= $(STAGING_IMAGE_PREFIX)/kwok-operator
CLUSTER_IMAGE ?= $(STAGING_IMAGE_PREFIX)/cluster
endif
@@ -76,7 +78,7 @@ IMAGE_PLATFORMS ?= linux/amd64 linux/arm64
BINARY_PLATFORMS ?= linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 windows/arm64
-MANIFESTS ?= kwok kwokctl stage/fast metrics/usage
+MANIFESTS ?= kwok kwokctl operator stage/fast metrics/usage
BUILDER ?= docker
DOCKER_CLI_EXPERIMENTAL ?= enabled
@@ -187,6 +189,31 @@ cross-image:
--builder=${BUILDER} \
--push=${PUSH}
+## operator-image: Build kwok operator image
+.PHONY: operator-image
+operator-image:
+ @./images/kwok-operator/build.sh \
+ $(addprefix --extra-tag=, $(EXTRA_TAGS)) \
+ --image=${KWOK_OPERATOR_IMAGE} \
+ --version=${VERSION} \
+ --staging-prefix=${STAGING_PREFIX} \
+ --dry-run=${DRY_RUN} \
+ --builder=${BUILDER} \
+ --push=${PUSH}
+
+## cross-operator-image: Build kwok operator images for all supported platforms
+.PHONY: cross-operator-image
+cross-operator-image:
+ @./images/kwok-operator/build.sh \
+ $(addprefix --platform=, $(IMAGE_PLATFORMS)) \
+ $(addprefix --extra-tag=, $(EXTRA_TAGS)) \
+ --image=${KWOK_OPERATOR_IMAGE} \
+ --version=${VERSION} \
+ --staging-prefix=${STAGING_PREFIX} \
+ --dry-run=${DRY_RUN} \
+ --builder=${BUILDER} \
+ --push=${PUSH}
+
## cluster-image: Build cluster image
.PHONY: cluster-image
cluster-image:
diff --git a/charts/operator/.helmignore b/charts/operator/.helmignore
new file mode 100644
index 0000000000..6b8c0abba4
--- /dev/null
+++ b/charts/operator/.helmignore
@@ -0,0 +1 @@
+.helmignore
diff --git a/charts/operator/Chart.yaml b/charts/operator/Chart.yaml
new file mode 100644
index 0000000000..fda41169c9
--- /dev/null
+++ b/charts/operator/Chart.yaml
@@ -0,0 +1,17 @@
+apiVersion: v2
+description: KWOK Operator
+type: application
+home: https://kwok.sigs.k8s.io
+icon: https://github.com/kubernetes-sigs/kwok/raw/main/logo/kwok.png
+keywords:
+- kubernetes
+- kwok
+- operator
+sources:
+- https://github.com/kubernetes-sigs/kwok
+name: operator
+maintainers:
+- name: wzshiming
+ email: wzshiming@hotmail.com
+appVersion: v0.6.0
+version: 0.0.1
diff --git a/charts/operator/README.md b/charts/operator/README.md
new file mode 100644
index 0000000000..2e136aeff1
--- /dev/null
+++ b/charts/operator/README.md
@@ -0,0 +1,49 @@
+# KWOK (Kubernetes WithOut Kubelet)
+
+[KWOK](https://kwok.sigs.k8s.io/) - Simulates thousands of Nodes and Clusters.
+
+## Installing the Chart
+
+Before you can install the chart you will need to add the `kwok` repo to [Helm](https://helm.sh/).
+
+```shell
+helm repo add kwok https://kwok.sigs.k8s.io/charts/
+```
+
+After you've installed the repo you can install the chart.
+
+```shell
+helm upgrade --namespace kube-system --install kwok kwok/kwok
+```
+
+Set up default stage policy (required)
+> NOTE: This configures the pod/node emulation behavior, if not it will do nothing.
+
+```shell
+helm upgrade --install kwok kwok/stage-fast
+```
+
+Set up default metrics usage policy (optional)
+
+```shell
+helm upgrade --install kwok kwok/metrics-usage
+```
+
+## Configuration
+
+The following table lists the configurable parameters of the kwok chart and their default values.
+
+| Key | Type | Default | Description |
+|-----|------|---------|-------------|
+| affinity | object | `{}` | |
+| fullnameOverride | string | `"kwok-controller"` | Override the `fullname` of the chart. |
+| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy. |
+| image.repository | string | `"registry.k8s.io/kwok/kwok"` | Image repository. |
+| image.tag | string | `""` | Overrides the image tag whose default is {{ .Chart.AppVersion }}. |
+| imagePullSecrets | list | `[]` | Image pull secrets. |
+| nameOverride | string | `""` | Override the `name` of the chart. |
+| nodeSelector | object | `{}` | |
+| podSecurityContext | object | `{}` | |
+| replicas | int | `1` | The replica count for Deployment. |
+| resources | object | `{}` | |
+| securityContext | object | `{}` | |
diff --git a/charts/operator/README.md.gotmpl b/charts/operator/README.md.gotmpl
new file mode 100644
index 0000000000..4b66504e33
--- /dev/null
+++ b/charts/operator/README.md.gotmpl
@@ -0,0 +1,9 @@
+# {{ template "chart.description" . }}
+
+{{ template "extra.usage" . }}
+
+## Configuration
+
+The following table lists the configurable parameters of the kwok chart and their default values.
+
+{{ template "chart.valuesTable" . }}
diff --git a/charts/operator/crds b/charts/operator/crds
new file mode 120000
index 0000000000..8890a961f7
--- /dev/null
+++ b/charts/operator/crds
@@ -0,0 +1 @@
+../../kustomize/operator/crd/bases
\ No newline at end of file
diff --git a/charts/operator/templates/_helpers.tpl b/charts/operator/templates/_helpers.tpl
new file mode 100644
index 0000000000..f3d96e7bbf
--- /dev/null
+++ b/charts/operator/templates/_helpers.tpl
@@ -0,0 +1,62 @@
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "kwok.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "kwok.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "kwok.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "kwok.labels" -}}
+helm.sh/chart: {{ include "kwok.chart" . }}
+{{ include "kwok.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "kwok.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "kwok.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "kwok.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create }}
+{{- default (include "kwok.fullname" .) .Values.serviceAccount.name }}
+{{- else }}
+{{- default "default" .Values.serviceAccount.name }}
+{{- end }}
+{{- end }}
diff --git a/charts/operator/templates/deployment.yaml b/charts/operator/templates/deployment.yaml
new file mode 100644
index 0000000000..6a747a3e15
--- /dev/null
+++ b/charts/operator/templates/deployment.yaml
@@ -0,0 +1,46 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: {{ include "kwok.fullname" . }}
+ labels:
+ {{- include "kwok.labels" . | nindent 4 }}
+spec:
+ replicas: {{ .Values.replicas }}
+ selector:
+ matchLabels:
+ {{- include "kwok.selectorLabels" . | nindent 6 }}
+ template:
+ metadata:
+ labels:
+ {{- include "kwok.selectorLabels" . | nindent 8 }}
+ spec:
+ {{- with .Values.imagePullSecrets }}
+ imagePullSecrets:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ serviceAccountName: {{ include "kwok.fullname" . }}
+ {{- with .Values.podSecurityContext }}
+ securityContext:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ restartPolicy: Always
+ containers:
+ - name: {{ .Chart.Name }}
+ image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+ imagePullPolicy: {{ .Values.image.pullPolicy }}
+ securityContext:
+ {{- toYaml .Values.securityContext | nindent 10 }}
+ resources:
+ {{- toYaml .Values.resources | nindent 10 }}
+ {{- with .Values.nodeSelector }}
+ nodeSelector:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.affinity }}
+ affinity:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.tolerations }}
+ tolerations:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
diff --git a/charts/operator/templates/role.yaml b/charts/operator/templates/role.yaml
new file mode 100644
index 0000000000..9bf52cbd92
--- /dev/null
+++ b/charts/operator/templates/role.yaml
@@ -0,0 +1,47 @@
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: kwok-operator
+rules:
+- apiGroups:
+ - ""
+ resources:
+ - pods
+ verbs:
+ - create
+ - delete
+ - get
+- apiGroups:
+ - apps
+ resources:
+ - deployments
+ verbs:
+ - get
+ - list
+ - watch
+- apiGroups:
+ - apps
+ resources:
+ - deployments/scale
+ verbs:
+ - update
+- apiGroups:
+ - kwok.x-k8s.io
+ resources:
+ - controllers
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups:
+ - kwok.x-k8s.io
+ resources:
+ - controllers/status
+ verbs:
+ - patch
+ - update
diff --git a/charts/operator/templates/role_binding.yaml b/charts/operator/templates/role_binding.yaml
new file mode 100644
index 0000000000..2c3bf117f0
--- /dev/null
+++ b/charts/operator/templates/role_binding.yaml
@@ -0,0 +1,12 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: kwok-operator
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: kwok-operator
+subjects:
+- kind: ServiceAccount
+ name: kwok-operator
+ namespace: {{ .Release.Namespace }}
diff --git a/charts/operator/templates/service_account.yaml b/charts/operator/templates/service_account.yaml
new file mode 100644
index 0000000000..f25ba26953
--- /dev/null
+++ b/charts/operator/templates/service_account.yaml
@@ -0,0 +1,5 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: kwok-operator
+ namespace: {{ .Release.Namespace }}
diff --git a/charts/operator/values.yaml b/charts/operator/values.yaml
new file mode 100644
index 0000000000..4c8bccf78d
--- /dev/null
+++ b/charts/operator/values.yaml
@@ -0,0 +1,26 @@
+image:
+ # -- Image pull policy.
+ pullPolicy: IfNotPresent
+ # -- Image repository.
+ repository: registry.k8s.io/kwok/operator
+ # -- Overrides the image tag whose default is {{ .Chart.AppVersion }}.
+ tag: ""
+
+# -- Image pull secrets.
+imagePullSecrets: []
+
+# -- Override the `name` of the chart.
+nameOverride: ""
+
+# -- Override the `fullname` of the chart.
+fullnameOverride: "kwok-operator"
+
+podSecurityContext: {}
+securityContext: {}
+
+nodeSelector: {}
+resources: {}
+affinity: {}
+
+# -- The replica count for Deployment.
+replicas: 1
diff --git a/cmd/kwok-operator/main.go b/cmd/kwok-operator/main.go
new file mode 100644
index 0000000000..aa764e1873
--- /dev/null
+++ b/cmd/kwok-operator/main.go
@@ -0,0 +1,53 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package main is the entry point for the kwok binary.
+package main
+
+import (
+ "os"
+
+ "github.com/spf13/pflag"
+
+ "sigs.k8s.io/kwok/pkg/config"
+ "sigs.k8s.io/kwok/pkg/log"
+ "sigs.k8s.io/kwok/pkg/operator/cmd"
+ "sigs.k8s.io/kwok/pkg/utils/signals"
+)
+
+func main() {
+ flagset := pflag.NewFlagSet("global", pflag.ContinueOnError)
+ flagset.ParseErrorsWhitelist.UnknownFlags = true
+ flagset.Usage = func() {}
+
+ ctx := signals.SetupSignalContext()
+ ctx, logger := log.InitFlags(ctx, flagset)
+
+ ctx, err := config.InitFlags(ctx, flagset)
+ if err != nil {
+ _, _ = os.Stderr.Write([]byte(flagset.FlagUsages()))
+ logger.Error("Init config flags", err)
+ os.Exit(1)
+ }
+
+ command := cmd.NewCommand(ctx)
+ command.PersistentFlags().AddFlagSet(flagset)
+ err = command.ExecuteContext(ctx)
+ if err != nil {
+ logger.Error("Execute exit", err)
+ os.Exit(1)
+ }
+}
diff --git a/hack/releases-helm-charts.sh b/hack/releases-helm-charts.sh
index 034df3e97b..deec0a15c2 100755
--- a/hack/releases-helm-charts.sh
+++ b/hack/releases-helm-charts.sh
@@ -85,5 +85,6 @@ chart_dir="./charts"
index_dir="${ROOT_DIR}/site/static/charts"
package_and_index "${index_dir}" "${chart_dir}/kwok" "kwok-chart" || :
+package_and_index "${index_dir}" "${chart_dir}/operator" "kwok-operator-chart" || :
package_and_index "${index_dir}" "${chart_dir}/stage-fast" "kwok-stage-fast-chart" || :
package_and_index "${index_dir}" "${chart_dir}/metrics-usage" "kwok-metrics-usage-chart" || :
diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh
index 838df32107..33afa30582 100755
--- a/hack/update-codegen.sh
+++ b/hack/update-codegen.sh
@@ -47,6 +47,7 @@ function gen() {
deepcopy-gen \
./pkg/apis/internalversion/ \
./pkg/apis/v1alpha1/ \
+ ./pkg/apis/operator/v1alpha1/ \
./pkg/apis/config/v1alpha1/ \
./pkg/apis/action/v1alpha1/ \
--output-file zz_generated.deepcopy.go \
@@ -54,6 +55,7 @@ function gen() {
echo "Generating defaulter"
defaulter-gen \
./pkg/apis/v1alpha1/ \
+ ./pkg/apis/operator/v1alpha1/ \
./pkg/apis/config/v1alpha1/ \
./pkg/apis/action/v1alpha1/ \
--output-file zz_generated.defaults.go \
@@ -70,6 +72,7 @@ function gen() {
--clientset-name versioned \
--input-base "" \
--input sigs.k8s.io/kwok/pkg/apis/v1alpha1 \
+ --input sigs.k8s.io/kwok/pkg/apis/operator/v1alpha1 \
--output-pkg sigs.k8s.io/kwok/pkg/client/clientset \
--output-dir ./pkg/client/clientset \
--go-header-file ./hack/boilerplate/boilerplate.generatego.txt \
diff --git a/hack/update-controllergen.sh b/hack/update-controllergen.sh
index b556a828a5..76f53b745b 100755
--- a/hack/update-controllergen.sh
+++ b/hack/update-controllergen.sh
@@ -36,6 +36,17 @@ function gen() {
paths=./pkg/apis/v1alpha1/ \
output:crd:artifacts:config=kustomize/crd/bases \
output:rbac:artifacts:config=kustomize/rbac
+
+ rm -rf \
+ "${ROOT_DIR}/kustomize/operator/crd/bases" \
+ "${ROOT_DIR}/kustomize/operator/rbac/rbac.yaml"
+ echo "Generating kwok-operator crd/rbac"
+ controller-gen \
+ rbac:roleName=kwok-operator \
+ crd:allowDangerousTypes=true \
+ paths=./pkg/apis/operator/v1alpha1/ \
+ output:crd:artifacts:config=kustomize/operator/crd/bases \
+ output:rbac:artifacts:config=kustomize/operator/rbac
}
cd "${ROOT_DIR}" && gen
diff --git a/hack/update-helm-charts.sh b/hack/update-helm-charts.sh
index 3c7479af57..f3669342a3 100755
--- a/hack/update-helm-charts.sh
+++ b/hack/update-helm-charts.sh
@@ -66,6 +66,10 @@ function sync() {
sync_object_to_chart kustomize/rbac/role_binding.yaml charts/kwok/templates/role_binding.yaml
sync_object_to_chart kustomize/rbac/service_account.yaml charts/kwok/templates/service_account.yaml
+ sync_object_to_chart kustomize/operator/rbac/role.yaml charts/operator/templates/role.yaml
+ sync_object_to_chart kustomize/operator/rbac/role_binding.yaml charts/operator/templates/role_binding.yaml
+ sync_object_to_chart kustomize/operator/rbac/service_account.yaml charts/operator/templates/service_account.yaml
+
sync_stage_to_chart kustomize/stage/pod/fast/pod-ready.yaml charts/stage-fast/templates/pod-ready.yaml
sync_stage_to_chart kustomize/stage/pod/fast/pod-complete.yaml charts/stage-fast/templates/pod-complete.yaml
sync_stage_to_chart kustomize/stage/pod/fast/pod-delete.yaml charts/stage-fast/templates/pod-delete.yaml
diff --git a/images/operator/Dockerfile b/images/operator/Dockerfile
new file mode 100644
index 0000000000..85ca4a1241
--- /dev/null
+++ b/images/operator/Dockerfile
@@ -0,0 +1,20 @@
+# Copyright 2024 The Kubernetes Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+ARG BASE_IMAGE=docker.io/library/alpine:3.20
+FROM --platform=$TARGETPLATFORM $BASE_IMAGE AS cache
+ARG TARGETPLATFORM
+COPY --chmod=0755 bin/$TARGETPLATFORM/kwok-operator /usr/local/bin/
+
+ENTRYPOINT ["/usr/local/bin/kwok-operator"]
diff --git a/images/operator/build.sh b/images/operator/build.sh
new file mode 100755
index 0000000000..8fe2f447d4
--- /dev/null
+++ b/images/operator/build.sh
@@ -0,0 +1,217 @@
+#!/usr/bin/env bash
+# Copyright 2024 The Kubernetes Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -o errexit
+set -o nounset
+set -o pipefail
+
+DIR="$(dirname "${BASH_SOURCE[0]}")"
+DIR="$(realpath "${DIR}")"
+ROOT_DIR="$(realpath "${DIR}/../..")"
+DOCKERFILE="$(echo "${DIR}/Dockerfile" | sed "s|^${ROOT_DIR}/|./|")"
+
+BASE_IMAGE=
+DRY_RUN=false
+PUSH=false
+IMAGES=()
+EXTRA_TAGS=()
+PLATFORMS=()
+VERSION=""
+STAGING_PREFIX=""
+BUILDER="docker"
+
+function usage() {
+ echo "Usage: ${0} [--help] [--version ] [--image ...] [--extra-tag ...] [--staging-prefix ] [--platform ...] [--push] [--dry-run] [--builder ]"
+ echo " --version is kwok version, is required"
+ echo " --image is image, is required"
+ echo " --extra-tag is extra tag"
+ echo " --staging-prefix is staging prefix for tag"
+ echo " --platform is multi-platform capable for image"
+ echo " --push will push image to registry"
+ echo " --dry-run just show what would be done"
+ echo " --builder specify image builder, default: ${BUILDER}. available options: docker, nerdctl, podman"
+ echo " --base-image specify base image, default: ${BASE_IMAGE}"
+}
+
+function args() {
+ local arg
+ while [[ $# -gt 0 ]]; do
+ arg="$1"
+ case "${arg}" in
+ --version | --version=*)
+ [[ "${arg#*=}" != "${arg}" ]] && VERSION="${arg#*=}" || { VERSION="${2}" && shift; } || :
+ shift
+ ;;
+ --image | --image=*)
+ [[ "${arg#*=}" != "${arg}" ]] && IMAGES+=("${arg#*=}") || { IMAGES+=("${2}") && shift; } || :
+ shift
+ ;;
+ --extra-tag | --extra-tag=*)
+ [[ "${arg#*=}" != "${arg}" ]] && EXTRA_TAGS+=("${arg#*=}") || { EXTRA_TAGS+=("${2}") && shift; } || :
+ shift
+ ;;
+ --staging-prefix | --staging-prefix=*)
+ [[ "${arg#*=}" != "${arg}" ]] && STAGING_PREFIX="${arg#*=}" || { STAGING_PREFIX="${2}" && shift; } || :
+ shift
+ ;;
+ --platform | --platform=*)
+ [[ "${arg#*=}" != "${arg}" ]] && PLATFORMS+=("${arg#*=}") || { PLATFORMS+=("${2}") && shift; } || :
+ shift
+ ;;
+ --push | --push=*)
+ [[ "${arg#*=}" != "${arg}" ]] && PUSH="${arg#*=}" || PUSH="true" || :
+ shift
+ ;;
+ --dry-run | --dry-run=*)
+ [[ "${arg#*=}" != "${arg}" ]] && DRY_RUN="${arg#*=}" || DRY_RUN="true" || :
+ shift
+ ;;
+ --builder | --builder=*)
+ [[ "${arg#*=}" != "${arg}" ]] && BUILDER="${arg#*=}" || { BUILDER="${2}" && shift; } || :
+ shift
+ ;;
+ --base-image | --base-image=*)
+ [[ "${arg#*=}" != "${arg}" ]] && BASE_IMAGE="${arg#*=}" || { BASE_IMAGE="${2}" && shift; } || :
+ shift
+ ;;
+ --help)
+ usage
+ exit 0
+ ;;
+ *)
+ echo "Unknown argument: ${arg}"
+ usage
+ exit 1
+ ;;
+ esac
+ done
+
+ if [[ "${VERSION}" == "" ]]; then
+ echo "--version is required"
+ usage
+ exit 1
+ fi
+
+ if [[ "${#IMAGES}" -eq 0 ]]; then
+ echo "--image is required"
+ usage
+ exit 1
+ fi
+
+ if [[ "${#PLATFORMS[*]}" -eq 0 ]]; then
+ PLATFORMS+=(
+ linux/amd64
+ )
+ fi
+}
+
+function dry_run() {
+ echo "$*"
+ if [[ "${DRY_RUN}" != "true" ]]; then
+ eval "$*"
+ fi
+}
+
+function main() {
+ local extra_args
+ local platform_args
+ local images
+ local tag
+
+ extra_args=()
+ images=()
+ for image in "${IMAGES[@]}"; do
+ tag="${VERSION}"
+ if [[ "${STAGING_PREFIX}" != "" ]]; then
+ tag="${STAGING_PREFIX}-${VERSION}"
+ fi
+ extra_args+=("--tag=${image}:${tag}")
+ images+=("${image}:${tag}")
+
+ if [[ "${#EXTRA_TAGS[@]}" -ne 0 ]]; then
+ for extra_tag in "${EXTRA_TAGS[@]}"; do
+ tag="${extra_tag}"
+ if [[ "${STAGING_PREFIX}" != "" ]]; then
+ tag="${STAGING_PREFIX}-${extra_tag}"
+ fi
+ extra_args+=("--tag=${image}:${tag}")
+ images+=("${image}:${tag}")
+ done
+ fi
+ done
+
+ if [[ "${BASE_IMAGE}" != "" ]]; then
+ extra_args+=("--build-arg=BASE_IMAGE=${BASE_IMAGE}")
+ fi
+
+ for platform in "${PLATFORMS[@]}"; do
+ extra_args+=("--platform=${platform}")
+ platform_args+=("--platform=${platform}")
+ done
+
+ if [[ "${BUILDER}" == "nerdctl" ]]; then
+ build_with_nerdctl "${extra_args[@]}"
+ if [[ "${PUSH}" == "true" ]]; then
+ for image in "${images[@]}"; do
+ dry_run nerdctl push "${platform_args[@]}" "${image}"
+ done
+ fi
+ elif [[ "${BUILDER}" == "podman" ]]; then
+ build_with_podman "${extra_args[@]}"
+ if [[ "${PUSH}" == "true" ]]; then
+ for image in "${images[@]}"; do
+ dry_run podman push "${platform_args[@]}" "${image}"
+ done
+ fi
+ else
+ if [[ "${PUSH}" == "true" ]]; then
+ extra_args+=("--push")
+ else
+ extra_args+=("--load")
+ fi
+ build_with_docker "${extra_args[@]}"
+ fi
+}
+
+function build_with_docker() {
+ local extra_args
+ extra_args=("$@")
+ dry_run docker buildx build \
+ "${extra_args[@]}" \
+ -f "${DOCKERFILE}" \
+ .
+}
+
+function build_with_nerdctl() {
+ local extra_args
+ extra_args=("$@")
+ dry_run nerdctl build \
+ "${extra_args[@]}" \
+ -f "${DOCKERFILE}" \
+ .
+}
+
+function build_with_podman() {
+ local extra_args
+ extra_args=("$@")
+ dry_run podman build \
+ "${extra_args[@]}" \
+ -f "${DOCKERFILE}" \
+ .
+}
+
+args "$@"
+
+cd "${ROOT_DIR}" && main
diff --git a/kustomize/operator/controller.yaml b/kustomize/operator/controller.yaml
new file mode 100644
index 0000000000..28caa81c16
--- /dev/null
+++ b/kustomize/operator/controller.yaml
@@ -0,0 +1,6 @@
+apiVersion: operator.kwok.x-k8s.io/v1alpha1
+kind: Controller
+metadata:
+ name: kwok-controller
+ namespace: kube-system
+spec: {}
diff --git a/kustomize/operator/crd/bases/operator.kwok.x-k8s.io_controllers.yaml b/kustomize/operator/crd/bases/operator.kwok.x-k8s.io_controllers.yaml
new file mode 100644
index 0000000000..dda7ed3c0b
--- /dev/null
+++ b/kustomize/operator/crd/bases/operator.kwok.x-k8s.io_controllers.yaml
@@ -0,0 +1,128 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.16.1
+ name: controllers.operator.kwok.x-k8s.io
+spec:
+ group: operator.kwok.x-k8s.io
+ names:
+ kind: Controller
+ listKind: ControllerList
+ plural: controllers
+ singular: controller
+ scope: Namespaced
+ versions:
+ - name: v1alpha1
+ schema:
+ openAPIV3Schema:
+ description: Controller provides controller configuration for a single pod.
+ properties:
+ apiVersion:
+ description: |-
+ APIVersion defines the versioned schema of this representation of an object.
+ Servers should convert recognized schemas to the latest internal value, and
+ may reject unrecognized values.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+ type: string
+ kind:
+ description: |-
+ Kind is a string value representing the REST resource this object represents.
+ Servers may infer this from the endpoint the client submits requests to.
+ Cannot be updated.
+ In CamelCase.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ type: string
+ metadata:
+ type: object
+ spec:
+ description: Spec holds spec for controller
+ properties:
+ manages:
+ items:
+ description: TargetResourceRef specifies the kind and version of
+ the resource.
+ properties:
+ apiGroup:
+ default: v1
+ description: APIGroup of the referent.
+ type: string
+ kind:
+ description: Kind of the referent.
+ type: string
+ name:
+ description: Name of the resource
+ type: string
+ namespace:
+ description: Namespace of the resource
+ type: string
+ required:
+ - kind
+ type: object
+ type: array
+ type: object
+ status:
+ description: Status holds status for controller
+ properties:
+ conditions:
+ description: Conditions holds conditions for controller
+ items:
+ description: Condition contains details for one aspect of the current
+ state of this API Resource.
+ properties:
+ lastTransitionTime:
+ description: |-
+ LastTransitionTime is the last time the condition transitioned from one status to another.
+ This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
+ format: date-time
+ type: string
+ message:
+ description: |-
+ Message is a human readable message indicating details about the transition.
+ This may be an empty string.
+ maxLength: 32768
+ type: string
+ reason:
+ description: |-
+ Reason contains a programmatic identifier indicating the reason for the condition's last transition.
+ Producers of specific condition types may define expected values and meanings for this field,
+ and whether the values are considered a guaranteed API.
+ The value should be a CamelCase string.
+ This field may not be empty.
+ maxLength: 1024
+ minLength: 1
+ pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+ type: string
+ status:
+ description: Status of the condition
+ type: string
+ type:
+ description: |-
+ Type of condition in CamelCase or in foo.example.com/CamelCase.
+ Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be
+ useful (see .node.status.conditions), the ability to deconflict is important.
+ The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
+ maxLength: 316
+ pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+ type: string
+ required:
+ - lastTransitionTime
+ - message
+ - reason
+ - status
+ - type
+ type: object
+ type: array
+ x-kubernetes-list-map-keys:
+ - type
+ x-kubernetes-list-type: map
+ type: object
+ required:
+ - metadata
+ - spec
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
diff --git a/kustomize/operator/crd/kustomization.yaml b/kustomize/operator/crd/kustomization.yaml
new file mode 100644
index 0000000000..7247c86ab1
--- /dev/null
+++ b/kustomize/operator/crd/kustomization.yaml
@@ -0,0 +1,4 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+resources:
+- bases/operator.kwok.x-k8s.io_controllers.yaml
diff --git a/kustomize/operator/deployment.yaml b/kustomize/operator/deployment.yaml
new file mode 100644
index 0000000000..b0644b249b
--- /dev/null
+++ b/kustomize/operator/deployment.yaml
@@ -0,0 +1,14 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: kwok-operator
+spec:
+ replicas: 1
+ template:
+ spec:
+ containers:
+ - name: kwok-operator
+ image: registry.k8s.io/kwok/kwok-operator
+ imagePullPolicy: IfNotPresent
+ serviceAccountName: kwok-operator
+ restartPolicy: Always
diff --git a/kustomize/operator/kustomization.yaml b/kustomize/operator/kustomization.yaml
new file mode 100644
index 0000000000..779133855e
--- /dev/null
+++ b/kustomize/operator/kustomization.yaml
@@ -0,0 +1,14 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+
+namespace: kube-system
+
+resources:
+- crd
+- rbac
+- deployment.yaml
+
+labels:
+- includeSelectors: true
+ pairs:
+ app: kwok-operator
diff --git a/kustomize/operator/rbac/kustomization.yaml b/kustomize/operator/rbac/kustomization.yaml
new file mode 100644
index 0000000000..11a2ab55b5
--- /dev/null
+++ b/kustomize/operator/rbac/kustomization.yaml
@@ -0,0 +1,6 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+resources:
+- service_account.yaml
+- role.yaml
+- role_binding.yaml
diff --git a/kustomize/operator/rbac/role.yaml b/kustomize/operator/rbac/role.yaml
new file mode 100644
index 0000000000..9bf52cbd92
--- /dev/null
+++ b/kustomize/operator/rbac/role.yaml
@@ -0,0 +1,47 @@
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: kwok-operator
+rules:
+- apiGroups:
+ - ""
+ resources:
+ - pods
+ verbs:
+ - create
+ - delete
+ - get
+- apiGroups:
+ - apps
+ resources:
+ - deployments
+ verbs:
+ - get
+ - list
+ - watch
+- apiGroups:
+ - apps
+ resources:
+ - deployments/scale
+ verbs:
+ - update
+- apiGroups:
+ - kwok.x-k8s.io
+ resources:
+ - controllers
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups:
+ - kwok.x-k8s.io
+ resources:
+ - controllers/status
+ verbs:
+ - patch
+ - update
diff --git a/kustomize/operator/rbac/role_binding.yaml b/kustomize/operator/rbac/role_binding.yaml
new file mode 100644
index 0000000000..70f2c7b103
--- /dev/null
+++ b/kustomize/operator/rbac/role_binding.yaml
@@ -0,0 +1,12 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: kwok-operator
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: kwok-operator
+subjects:
+- kind: ServiceAccount
+ name: kwok-operator
+ namespace: kube-system
diff --git a/kustomize/operator/rbac/service_account.yaml b/kustomize/operator/rbac/service_account.yaml
new file mode 100644
index 0000000000..74289392f7
--- /dev/null
+++ b/kustomize/operator/rbac/service_account.yaml
@@ -0,0 +1,5 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: kwok-operator
+ namespace: kube-system
diff --git a/pkg/apis/internalversion/controller_types.go b/pkg/apis/internalversion/controller_types.go
new file mode 100644
index 0000000000..a8a78ea53d
--- /dev/null
+++ b/pkg/apis/internalversion/controller_types.go
@@ -0,0 +1,49 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package internalversion
+
+import (
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// Controller provides controller configuration for a single pod.
+type Controller struct {
+ // Standard list metadata.
+ // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
+ metav1.ObjectMeta
+ // Spec holds spec for controller
+ Spec ControllerSpec
+}
+
+// ControllerSpec holds spec for controller.
+type ControllerSpec struct {
+ Manages []TargetResourceRef
+}
+
+// TargetResourceRef specifies the kind and version of the resource.
+type TargetResourceRef struct {
+ // APIGroup of the referent.
+ // +default="v1"
+ // +kubebuilder:default="v1"
+ APIGroup string
+ // Kind of the referent.
+ Kind string
+ // Namespace of the resource
+ Namespace *string
+ // Name of the resource
+ Name *string
+}
diff --git a/pkg/apis/internalversion/doc.go b/pkg/apis/internalversion/doc.go
index f113b4c268..223a5e53a9 100644
--- a/pkg/apis/internalversion/doc.go
+++ b/pkg/apis/internalversion/doc.go
@@ -17,6 +17,7 @@ limitations under the License.
// +k8s:deepcopy-gen=package
// +k8s:defaulter-gen=TypeMeta
// +k8s:conversion-gen=sigs.k8s.io/kwok/pkg/apis/v1alpha1
+// +k8s:conversion-gen=sigs.k8s.io/kwok/pkg/apis/operator/v1alpha1
// +k8s:conversion-gen=sigs.k8s.io/kwok/pkg/apis/config/v1alpha1
// Package internalversion implements the internal apiVersion of kwok's configuration
diff --git a/pkg/apis/internalversion/zz_generated.conversion.go b/pkg/apis/internalversion/zz_generated.conversion.go
index b4d243b9b6..79aa707c7f 100644
--- a/pkg/apis/internalversion/zz_generated.conversion.go
+++ b/pkg/apis/internalversion/zz_generated.conversion.go
@@ -30,6 +30,7 @@ import (
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
configv1alpha1 "sigs.k8s.io/kwok/pkg/apis/config/v1alpha1"
+ operatorv1alpha1 "sigs.k8s.io/kwok/pkg/apis/operator/v1alpha1"
v1alpha1 "sigs.k8s.io/kwok/pkg/apis/v1alpha1"
)
@@ -200,6 +201,26 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
+ if err := s.AddGeneratedConversionFunc((*Controller)(nil), (*operatorv1alpha1.Controller)(nil), func(a, b interface{}, scope conversion.Scope) error {
+ return Convert_internalversion_Controller_To_v1alpha1_Controller(a.(*Controller), b.(*operatorv1alpha1.Controller), scope)
+ }); err != nil {
+ return err
+ }
+ if err := s.AddGeneratedConversionFunc((*operatorv1alpha1.Controller)(nil), (*Controller)(nil), func(a, b interface{}, scope conversion.Scope) error {
+ return Convert_v1alpha1_Controller_To_internalversion_Controller(a.(*operatorv1alpha1.Controller), b.(*Controller), scope)
+ }); err != nil {
+ return err
+ }
+ if err := s.AddGeneratedConversionFunc((*ControllerSpec)(nil), (*operatorv1alpha1.ControllerSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+ return Convert_internalversion_ControllerSpec_To_v1alpha1_ControllerSpec(a.(*ControllerSpec), b.(*operatorv1alpha1.ControllerSpec), scope)
+ }); err != nil {
+ return err
+ }
+ if err := s.AddGeneratedConversionFunc((*operatorv1alpha1.ControllerSpec)(nil), (*ControllerSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+ return Convert_v1alpha1_ControllerSpec_To_internalversion_ControllerSpec(a.(*operatorv1alpha1.ControllerSpec), b.(*ControllerSpec), scope)
+ }); err != nil {
+ return err
+ }
if err := s.AddGeneratedConversionFunc((*Env)(nil), (*configv1alpha1.Env)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_internalversion_Env_To_v1alpha1_Env(a.(*Env), b.(*configv1alpha1.Env), scope)
}); err != nil {
@@ -645,6 +666,16 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
+ if err := s.AddGeneratedConversionFunc((*TargetResourceRef)(nil), (*operatorv1alpha1.TargetResourceRef)(nil), func(a, b interface{}, scope conversion.Scope) error {
+ return Convert_internalversion_TargetResourceRef_To_v1alpha1_TargetResourceRef(a.(*TargetResourceRef), b.(*operatorv1alpha1.TargetResourceRef), scope)
+ }); err != nil {
+ return err
+ }
+ if err := s.AddGeneratedConversionFunc((*operatorv1alpha1.TargetResourceRef)(nil), (*TargetResourceRef)(nil), func(a, b interface{}, scope conversion.Scope) error {
+ return Convert_v1alpha1_TargetResourceRef_To_internalversion_TargetResourceRef(a.(*operatorv1alpha1.TargetResourceRef), b.(*TargetResourceRef), scope)
+ }); err != nil {
+ return err
+ }
if err := s.AddGeneratedConversionFunc((*Volume)(nil), (*configv1alpha1.Volume)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_internalversion_Volume_To_v1alpha1_Volume(a.(*Volume), b.(*configv1alpha1.Volume), scope)
}); err != nil {
@@ -1199,6 +1230,54 @@ func Convert_v1alpha1_ComponentPatches_To_internalversion_ComponentPatches(in *c
return autoConvert_v1alpha1_ComponentPatches_To_internalversion_ComponentPatches(in, out, s)
}
+func autoConvert_internalversion_Controller_To_v1alpha1_Controller(in *Controller, out *operatorv1alpha1.Controller, s conversion.Scope) error {
+ out.ObjectMeta = in.ObjectMeta
+ if err := Convert_internalversion_ControllerSpec_To_v1alpha1_ControllerSpec(&in.Spec, &out.Spec, s); err != nil {
+ return err
+ }
+ return nil
+}
+
+// Convert_internalversion_Controller_To_v1alpha1_Controller is an autogenerated conversion function.
+func Convert_internalversion_Controller_To_v1alpha1_Controller(in *Controller, out *operatorv1alpha1.Controller, s conversion.Scope) error {
+ return autoConvert_internalversion_Controller_To_v1alpha1_Controller(in, out, s)
+}
+
+func autoConvert_v1alpha1_Controller_To_internalversion_Controller(in *operatorv1alpha1.Controller, out *Controller, s conversion.Scope) error {
+ // INFO: in.TypeMeta opted out of conversion generation
+ out.ObjectMeta = in.ObjectMeta
+ if err := Convert_v1alpha1_ControllerSpec_To_internalversion_ControllerSpec(&in.Spec, &out.Spec, s); err != nil {
+ return err
+ }
+ // INFO: in.Status opted out of conversion generation
+ return nil
+}
+
+// Convert_v1alpha1_Controller_To_internalversion_Controller is an autogenerated conversion function.
+func Convert_v1alpha1_Controller_To_internalversion_Controller(in *operatorv1alpha1.Controller, out *Controller, s conversion.Scope) error {
+ return autoConvert_v1alpha1_Controller_To_internalversion_Controller(in, out, s)
+}
+
+func autoConvert_internalversion_ControllerSpec_To_v1alpha1_ControllerSpec(in *ControllerSpec, out *operatorv1alpha1.ControllerSpec, s conversion.Scope) error {
+ out.Manages = *(*[]operatorv1alpha1.TargetResourceRef)(unsafe.Pointer(&in.Manages))
+ return nil
+}
+
+// Convert_internalversion_ControllerSpec_To_v1alpha1_ControllerSpec is an autogenerated conversion function.
+func Convert_internalversion_ControllerSpec_To_v1alpha1_ControllerSpec(in *ControllerSpec, out *operatorv1alpha1.ControllerSpec, s conversion.Scope) error {
+ return autoConvert_internalversion_ControllerSpec_To_v1alpha1_ControllerSpec(in, out, s)
+}
+
+func autoConvert_v1alpha1_ControllerSpec_To_internalversion_ControllerSpec(in *operatorv1alpha1.ControllerSpec, out *ControllerSpec, s conversion.Scope) error {
+ out.Manages = *(*[]TargetResourceRef)(unsafe.Pointer(&in.Manages))
+ return nil
+}
+
+// Convert_v1alpha1_ControllerSpec_To_internalversion_ControllerSpec is an autogenerated conversion function.
+func Convert_v1alpha1_ControllerSpec_To_internalversion_ControllerSpec(in *operatorv1alpha1.ControllerSpec, out *ControllerSpec, s conversion.Scope) error {
+ return autoConvert_v1alpha1_ControllerSpec_To_internalversion_ControllerSpec(in, out, s)
+}
+
func autoConvert_internalversion_Env_To_v1alpha1_Env(in *Env, out *configv1alpha1.Env, s conversion.Scope) error {
out.Name = in.Name
out.Value = in.Value
@@ -2664,6 +2743,32 @@ func Convert_v1alpha1_StageSpec_To_internalversion_StageSpec(in *v1alpha1.StageS
return autoConvert_v1alpha1_StageSpec_To_internalversion_StageSpec(in, out, s)
}
+func autoConvert_internalversion_TargetResourceRef_To_v1alpha1_TargetResourceRef(in *TargetResourceRef, out *operatorv1alpha1.TargetResourceRef, s conversion.Scope) error {
+ out.APIGroup = in.APIGroup
+ out.Kind = in.Kind
+ out.Namespace = (*string)(unsafe.Pointer(in.Namespace))
+ out.Name = (*string)(unsafe.Pointer(in.Name))
+ return nil
+}
+
+// Convert_internalversion_TargetResourceRef_To_v1alpha1_TargetResourceRef is an autogenerated conversion function.
+func Convert_internalversion_TargetResourceRef_To_v1alpha1_TargetResourceRef(in *TargetResourceRef, out *operatorv1alpha1.TargetResourceRef, s conversion.Scope) error {
+ return autoConvert_internalversion_TargetResourceRef_To_v1alpha1_TargetResourceRef(in, out, s)
+}
+
+func autoConvert_v1alpha1_TargetResourceRef_To_internalversion_TargetResourceRef(in *operatorv1alpha1.TargetResourceRef, out *TargetResourceRef, s conversion.Scope) error {
+ out.APIGroup = in.APIGroup
+ out.Kind = in.Kind
+ out.Namespace = (*string)(unsafe.Pointer(in.Namespace))
+ out.Name = (*string)(unsafe.Pointer(in.Name))
+ return nil
+}
+
+// Convert_v1alpha1_TargetResourceRef_To_internalversion_TargetResourceRef is an autogenerated conversion function.
+func Convert_v1alpha1_TargetResourceRef_To_internalversion_TargetResourceRef(in *operatorv1alpha1.TargetResourceRef, out *TargetResourceRef, s conversion.Scope) error {
+ return autoConvert_v1alpha1_TargetResourceRef_To_internalversion_TargetResourceRef(in, out, s)
+}
+
func autoConvert_internalversion_Volume_To_v1alpha1_Volume(in *Volume, out *configv1alpha1.Volume, s conversion.Scope) error {
out.Name = in.Name
if err := v1.Convert_bool_To_Pointer_bool(&in.ReadOnly, &out.ReadOnly, s); err != nil {
diff --git a/pkg/apis/internalversion/zz_generated.deepcopy.go b/pkg/apis/internalversion/zz_generated.deepcopy.go
index fb56747cbf..ea8967775c 100644
--- a/pkg/apis/internalversion/zz_generated.deepcopy.go
+++ b/pkg/apis/internalversion/zz_generated.deepcopy.go
@@ -420,6 +420,47 @@ func (in *ComponentPatches) DeepCopy() *ComponentPatches {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Controller) DeepCopyInto(out *Controller) {
+ *out = *in
+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+ in.Spec.DeepCopyInto(&out.Spec)
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Controller.
+func (in *Controller) DeepCopy() *Controller {
+ if in == nil {
+ return nil
+ }
+ out := new(Controller)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ControllerSpec) DeepCopyInto(out *ControllerSpec) {
+ *out = *in
+ if in.Manages != nil {
+ in, out := &in.Manages, &out.Manages
+ *out = make([]TargetResourceRef, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControllerSpec.
+func (in *ControllerSpec) DeepCopy() *ControllerSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(ControllerSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Env) DeepCopyInto(out *Env) {
*out = *in
@@ -1499,6 +1540,32 @@ func (in *StageSpec) DeepCopy() *StageSpec {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *TargetResourceRef) DeepCopyInto(out *TargetResourceRef) {
+ *out = *in
+ if in.Namespace != nil {
+ in, out := &in.Namespace, &out.Namespace
+ *out = new(string)
+ **out = **in
+ }
+ if in.Name != nil {
+ in, out := &in.Name, &out.Name
+ *out = new(string)
+ **out = **in
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetResourceRef.
+func (in *TargetResourceRef) DeepCopy() *TargetResourceRef {
+ if in == nil {
+ return nil
+ }
+ out := new(TargetResourceRef)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Volume) DeepCopyInto(out *Volume) {
*out = *in
diff --git a/pkg/apis/operator/v1alpha1/condition.go b/pkg/apis/operator/v1alpha1/condition.go
new file mode 100644
index 0000000000..89cdf8f0d0
--- /dev/null
+++ b/pkg/apis/operator/v1alpha1/condition.go
@@ -0,0 +1,70 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+import (
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// Condition contains details for one aspect of the current state of this API Resource.
+type Condition struct {
+ // Type of condition in CamelCase or in foo.example.com/CamelCase.
+ // Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be
+ // useful (see .node.status.conditions), the ability to deconflict is important.
+ // The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
+ // +kubebuilder:validation:Required
+ // +kubebuilder:validation:Pattern=`^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$`
+ // +kubebuilder:validation:MaxLength=316
+ Type string `json:"type"`
+ // Status of the condition
+ // +kubebuilder:validation:Required
+ Status ConditionStatus `json:"status"`
+ // LastTransitionTime is the last time the condition transitioned from one status to another.
+ // This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
+ // +kubebuilder:validation:Required
+ // +kubebuilder:validation:Type=string
+ // +kubebuilder:validation:Format=date-time
+ LastTransitionTime metav1.Time `json:"lastTransitionTime"`
+ // Reason contains a programmatic identifier indicating the reason for the condition's last transition.
+ // Producers of specific condition types may define expected values and meanings for this field,
+ // and whether the values are considered a guaranteed API.
+ // The value should be a CamelCase string.
+ // This field may not be empty.
+ // +kubebuilder:validation:Required
+ // +kubebuilder:validation:MaxLength=1024
+ // +kubebuilder:validation:MinLength=1
+ // +kubebuilder:validation:Pattern=`^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$`
+ Reason string `json:"reason"`
+ // Message is a human readable message indicating details about the transition.
+ // This may be an empty string.
+ // +kubebuilder:validation:Required
+ // +kubebuilder:validation:MaxLength=32768
+ Message string `json:"message"`
+}
+
+// ConditionStatus is the status of a condition.
+// +enum
+type ConditionStatus string
+
+const (
+ // ConditionTrue means a resource is in the condition.
+ ConditionTrue ConditionStatus = "True"
+ // ConditionFalse means a resource is not in the condition.
+ ConditionFalse ConditionStatus = "False"
+ // ConditionUnknown means kubernetes can't decide if a resource is in the condition or not.
+ ConditionUnknown ConditionStatus = "Unknown"
+)
diff --git a/pkg/apis/operator/v1alpha1/controller_types.go b/pkg/apis/operator/v1alpha1/controller_types.go
new file mode 100644
index 0000000000..d0e130aa82
--- /dev/null
+++ b/pkg/apis/operator/v1alpha1/controller_types.go
@@ -0,0 +1,89 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+import (
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+const (
+ // ControllerKind is the kind of the Logs.
+ ControllerKind = "Controller"
+)
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+// +genclient
+// +kubebuilder:subresource:status
+// +kubebuilder:rbac:groups=kwok.x-k8s.io,resources=controllers,verbs=create;delete;get;list;patch;update;watch
+// +kubebuilder:rbac:groups=kwok.x-k8s.io,resources=controllers/status,verbs=update;patch
+
+// Controller provides controller configuration for a single pod.
+type Controller struct {
+ //+k8s:conversion-gen=false
+ metav1.TypeMeta `json:",inline"`
+ // Standard list metadata.
+ // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
+ metav1.ObjectMeta `json:"metadata"`
+ // Spec holds spec for controller
+ Spec ControllerSpec `json:"spec"`
+ // Status holds status for controller
+ //+k8s:conversion-gen=false
+ Status ControllerStatus `json:"status,omitempty"`
+}
+
+// ControllerStatus holds status for controller
+type ControllerStatus struct {
+ // Conditions holds conditions for controller
+ // +patchMergeKey=type
+ // +patchStrategy=merge
+ // +listType=map
+ // +listMapKey=type
+ Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
+}
+
+// ControllerSpec holds spec for controller.
+type ControllerSpec struct {
+ Manages []TargetResourceRef `json:"manages,omitempty"`
+}
+
+// TargetResourceRef specifies the kind and version of the resource.
+type TargetResourceRef struct {
+ // APIGroup of the referent.
+ // +default="v1"
+ // +kubebuilder:default="v1"
+ APIGroup string `json:"apiGroup,omitempty"`
+ // Kind of the referent.
+ Kind string `json:"kind"`
+ // Namespace of the resource
+ Namespace *string `json:"namespace,omitempty"`
+ // Name of the resource
+ Name *string `json:"name,omitempty"`
+}
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+// +kubebuilder:object:root=true
+
+// ControllerList contains a list of Controller
+type ControllerList struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ListMeta `json:"metadata,omitempty"`
+ Items []Controller `json:"items"`
+}
+
+func init() {
+ SchemeBuilder.Register(&Controller{}, &ControllerList{})
+}
diff --git a/pkg/apis/operator/v1alpha1/doc.go b/pkg/apis/operator/v1alpha1/doc.go
new file mode 100644
index 0000000000..21fac0bc9f
--- /dev/null
+++ b/pkg/apis/operator/v1alpha1/doc.go
@@ -0,0 +1,26 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// +k8s:deepcopy-gen=package
+// +k8s:defaulter-gen=TypeMeta
+// +groupName=operator.kwok.x-k8s.io
+
+// +kubebuilder:rbac:groups="",resources=pods,verbs=create;delete;get
+// +kubebuilder:rbac:groups="apps",resources=deployments,verbs=get;list;watch
+// +kubebuilder:rbac:groups="apps",resources=deployments/scale,verbs=update
+
+// Package v1alpha1 implements the v1alpha1 apiVersion of kwok's operator
+package v1alpha1
diff --git a/pkg/apis/operator/v1alpha1/groupversion_info.go b/pkg/apis/operator/v1alpha1/groupversion_info.go
new file mode 100644
index 0000000000..dc3f5ea26d
--- /dev/null
+++ b/pkg/apis/operator/v1alpha1/groupversion_info.go
@@ -0,0 +1,39 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// +kubebuilder:object:generate=true
+// +groupName=operator.kwok.x-k8s.io
+
+package v1alpha1
+
+import (
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "sigs.k8s.io/controller-runtime/pkg/scheme"
+)
+
+var (
+ // GroupVersion is group version used to register these objects
+ GroupVersion = schema.GroupVersion{
+ Group: "operator.kwok.x-k8s.io",
+ Version: "v1alpha1",
+ }
+
+ // SchemeBuilder is used to add go types to the GroupVersionKind scheme
+ SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
+
+ // AddToScheme adds the types in this group-version to the given scheme
+ AddToScheme = SchemeBuilder.AddToScheme
+)
diff --git a/pkg/apis/operator/v1alpha1/register.go b/pkg/apis/operator/v1alpha1/register.go
new file mode 100644
index 0000000000..d595f83dfb
--- /dev/null
+++ b/pkg/apis/operator/v1alpha1/register.go
@@ -0,0 +1,29 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+import (
+ "k8s.io/apimachinery/pkg/runtime/schema"
+)
+
+// SchemeGroupVersion is group version used to register these objects.
+var SchemeGroupVersion = GroupVersion
+
+// Resource takes an unqualified resource and returns a Group qualified GroupResource
+func Resource(resource string) schema.GroupResource {
+ return SchemeGroupVersion.WithResource(resource).GroupResource()
+}
diff --git a/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go
new file mode 100644
index 0000000000..ef91c99edf
--- /dev/null
+++ b/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go
@@ -0,0 +1,176 @@
+//go:build !ignore_autogenerated
+// +build !ignore_autogenerated
+
+/*
+Copyright The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by deepcopy-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ runtime "k8s.io/apimachinery/pkg/runtime"
+)
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Condition) DeepCopyInto(out *Condition) {
+ *out = *in
+ in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition.
+func (in *Condition) DeepCopy() *Condition {
+ if in == nil {
+ return nil
+ }
+ out := new(Condition)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Controller) DeepCopyInto(out *Controller) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+ in.Spec.DeepCopyInto(&out.Spec)
+ in.Status.DeepCopyInto(&out.Status)
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Controller.
+func (in *Controller) DeepCopy() *Controller {
+ if in == nil {
+ return nil
+ }
+ out := new(Controller)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Controller) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ControllerList) DeepCopyInto(out *ControllerList) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]Controller, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControllerList.
+func (in *ControllerList) DeepCopy() *ControllerList {
+ if in == nil {
+ return nil
+ }
+ out := new(ControllerList)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ControllerList) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ControllerSpec) DeepCopyInto(out *ControllerSpec) {
+ *out = *in
+ if in.Manages != nil {
+ in, out := &in.Manages, &out.Manages
+ *out = make([]TargetResourceRef, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControllerSpec.
+func (in *ControllerSpec) DeepCopy() *ControllerSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(ControllerSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ControllerStatus) DeepCopyInto(out *ControllerStatus) {
+ *out = *in
+ if in.Conditions != nil {
+ in, out := &in.Conditions, &out.Conditions
+ *out = make([]Condition, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControllerStatus.
+func (in *ControllerStatus) DeepCopy() *ControllerStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(ControllerStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *TargetResourceRef) DeepCopyInto(out *TargetResourceRef) {
+ *out = *in
+ if in.Namespace != nil {
+ in, out := &in.Namespace, &out.Namespace
+ *out = new(string)
+ **out = **in
+ }
+ if in.Name != nil {
+ in, out := &in.Name, &out.Name
+ *out = new(string)
+ **out = **in
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetResourceRef.
+func (in *TargetResourceRef) DeepCopy() *TargetResourceRef {
+ if in == nil {
+ return nil
+ }
+ out := new(TargetResourceRef)
+ in.DeepCopyInto(out)
+ return out
+}
diff --git a/pkg/apis/operator/v1alpha1/zz_generated.defaults.go b/pkg/apis/operator/v1alpha1/zz_generated.defaults.go
new file mode 100644
index 0000000000..6521b4f482
--- /dev/null
+++ b/pkg/apis/operator/v1alpha1/zz_generated.defaults.go
@@ -0,0 +1,51 @@
+//go:build !ignore_autogenerated
+// +build !ignore_autogenerated
+
+/*
+Copyright The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by defaulter-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ runtime "k8s.io/apimachinery/pkg/runtime"
+)
+
+// RegisterDefaults adds defaulters functions to the given scheme.
+// Public to allow building arbitrary schemes.
+// All generated defaulters are covering - they call all nested defaulters.
+func RegisterDefaults(scheme *runtime.Scheme) error {
+ scheme.AddTypeDefaultingFunc(&Controller{}, func(obj interface{}) { SetObjectDefaults_Controller(obj.(*Controller)) })
+ scheme.AddTypeDefaultingFunc(&ControllerList{}, func(obj interface{}) { SetObjectDefaults_ControllerList(obj.(*ControllerList)) })
+ return nil
+}
+
+func SetObjectDefaults_Controller(in *Controller) {
+ for i := range in.Spec.Manages {
+ a := &in.Spec.Manages[i]
+ if a.APIGroup == "" {
+ a.APIGroup = "v1"
+ }
+ }
+}
+
+func SetObjectDefaults_ControllerList(in *ControllerList) {
+ for i := range in.Items {
+ a := &in.Items[i]
+ SetObjectDefaults_Controller(a)
+ }
+}
diff --git a/pkg/client/clientset/versioned/clientset.go b/pkg/client/clientset/versioned/clientset.go
index 9eb643fe1f..8c801ae508 100644
--- a/pkg/client/clientset/versioned/clientset.go
+++ b/pkg/client/clientset/versioned/clientset.go
@@ -26,17 +26,20 @@ import (
rest "k8s.io/client-go/rest"
flowcontrol "k8s.io/client-go/util/flowcontrol"
kwokv1alpha1 "sigs.k8s.io/kwok/pkg/client/clientset/versioned/typed/apis/v1alpha1"
+ operatorv1alpha1 "sigs.k8s.io/kwok/pkg/client/clientset/versioned/typed/operator/v1alpha1"
)
type Interface interface {
Discovery() discovery.DiscoveryInterface
KwokV1alpha1() kwokv1alpha1.KwokV1alpha1Interface
+ OperatorV1alpha1() operatorv1alpha1.OperatorV1alpha1Interface
}
// Clientset contains the clients for groups.
type Clientset struct {
*discovery.DiscoveryClient
- kwokV1alpha1 *kwokv1alpha1.KwokV1alpha1Client
+ kwokV1alpha1 *kwokv1alpha1.KwokV1alpha1Client
+ operatorV1alpha1 *operatorv1alpha1.OperatorV1alpha1Client
}
// KwokV1alpha1 retrieves the KwokV1alpha1Client
@@ -44,6 +47,11 @@ func (c *Clientset) KwokV1alpha1() kwokv1alpha1.KwokV1alpha1Interface {
return c.kwokV1alpha1
}
+// OperatorV1alpha1 retrieves the OperatorV1alpha1Client
+func (c *Clientset) OperatorV1alpha1() operatorv1alpha1.OperatorV1alpha1Interface {
+ return c.operatorV1alpha1
+}
+
// Discovery retrieves the DiscoveryClient
func (c *Clientset) Discovery() discovery.DiscoveryInterface {
if c == nil {
@@ -92,6 +100,10 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset,
if err != nil {
return nil, err
}
+ cs.operatorV1alpha1, err = operatorv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient)
+ if err != nil {
+ return nil, err
+ }
cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
@@ -114,6 +126,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset {
func New(c rest.Interface) *Clientset {
var cs Clientset
cs.kwokV1alpha1 = kwokv1alpha1.New(c)
+ cs.operatorV1alpha1 = operatorv1alpha1.New(c)
cs.DiscoveryClient = discovery.NewDiscoveryClient(c)
return &cs
diff --git a/pkg/client/clientset/versioned/fake/clientset_generated.go b/pkg/client/clientset/versioned/fake/clientset_generated.go
index 919b2d874c..bf673c7630 100644
--- a/pkg/client/clientset/versioned/fake/clientset_generated.go
+++ b/pkg/client/clientset/versioned/fake/clientset_generated.go
@@ -27,6 +27,8 @@ import (
clientset "sigs.k8s.io/kwok/pkg/client/clientset/versioned"
kwokv1alpha1 "sigs.k8s.io/kwok/pkg/client/clientset/versioned/typed/apis/v1alpha1"
fakekwokv1alpha1 "sigs.k8s.io/kwok/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake"
+ operatorv1alpha1 "sigs.k8s.io/kwok/pkg/client/clientset/versioned/typed/operator/v1alpha1"
+ fakeoperatorv1alpha1 "sigs.k8s.io/kwok/pkg/client/clientset/versioned/typed/operator/v1alpha1/fake"
)
// NewSimpleClientset returns a clientset that will respond with the provided objects.
@@ -87,3 +89,8 @@ var (
func (c *Clientset) KwokV1alpha1() kwokv1alpha1.KwokV1alpha1Interface {
return &fakekwokv1alpha1.FakeKwokV1alpha1{Fake: &c.Fake}
}
+
+// OperatorV1alpha1 retrieves the OperatorV1alpha1Client
+func (c *Clientset) OperatorV1alpha1() operatorv1alpha1.OperatorV1alpha1Interface {
+ return &fakeoperatorv1alpha1.FakeOperatorV1alpha1{Fake: &c.Fake}
+}
diff --git a/pkg/client/clientset/versioned/fake/register.go b/pkg/client/clientset/versioned/fake/register.go
index 489b8a2caf..2cef8f1c30 100644
--- a/pkg/client/clientset/versioned/fake/register.go
+++ b/pkg/client/clientset/versioned/fake/register.go
@@ -24,6 +24,7 @@ import (
schema "k8s.io/apimachinery/pkg/runtime/schema"
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+ operatorv1alpha1 "sigs.k8s.io/kwok/pkg/apis/operator/v1alpha1"
kwokv1alpha1 "sigs.k8s.io/kwok/pkg/apis/v1alpha1"
)
@@ -32,6 +33,7 @@ var codecs = serializer.NewCodecFactory(scheme)
var localSchemeBuilder = runtime.SchemeBuilder{
kwokv1alpha1.AddToScheme,
+ operatorv1alpha1.AddToScheme,
}
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
diff --git a/pkg/client/clientset/versioned/scheme/register.go b/pkg/client/clientset/versioned/scheme/register.go
index 4f0832e113..a5f7a7c6f2 100644
--- a/pkg/client/clientset/versioned/scheme/register.go
+++ b/pkg/client/clientset/versioned/scheme/register.go
@@ -24,6 +24,7 @@ import (
schema "k8s.io/apimachinery/pkg/runtime/schema"
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+ operatorv1alpha1 "sigs.k8s.io/kwok/pkg/apis/operator/v1alpha1"
kwokv1alpha1 "sigs.k8s.io/kwok/pkg/apis/v1alpha1"
)
@@ -32,6 +33,7 @@ var Codecs = serializer.NewCodecFactory(Scheme)
var ParameterCodec = runtime.NewParameterCodec(Scheme)
var localSchemeBuilder = runtime.SchemeBuilder{
kwokv1alpha1.AddToScheme,
+ operatorv1alpha1.AddToScheme,
}
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
diff --git a/pkg/client/clientset/versioned/typed/operator/v1alpha1/controller.go b/pkg/client/clientset/versioned/typed/operator/v1alpha1/controller.go
new file mode 100644
index 0000000000..e181f2bb5e
--- /dev/null
+++ b/pkg/client/clientset/versioned/typed/operator/v1alpha1/controller.go
@@ -0,0 +1,69 @@
+/*
+Copyright The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ "context"
+
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ types "k8s.io/apimachinery/pkg/types"
+ watch "k8s.io/apimachinery/pkg/watch"
+ gentype "k8s.io/client-go/gentype"
+ v1alpha1 "sigs.k8s.io/kwok/pkg/apis/operator/v1alpha1"
+ scheme "sigs.k8s.io/kwok/pkg/client/clientset/versioned/scheme"
+)
+
+// ControllersGetter has a method to return a ControllerInterface.
+// A group's client should implement this interface.
+type ControllersGetter interface {
+ Controllers(namespace string) ControllerInterface
+}
+
+// ControllerInterface has methods to work with Controller resources.
+type ControllerInterface interface {
+ Create(ctx context.Context, controller *v1alpha1.Controller, opts v1.CreateOptions) (*v1alpha1.Controller, error)
+ Update(ctx context.Context, controller *v1alpha1.Controller, opts v1.UpdateOptions) (*v1alpha1.Controller, error)
+ // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
+ UpdateStatus(ctx context.Context, controller *v1alpha1.Controller, opts v1.UpdateOptions) (*v1alpha1.Controller, error)
+ Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
+ DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
+ Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.Controller, error)
+ List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.ControllerList, error)
+ Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
+ Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Controller, err error)
+ ControllerExpansion
+}
+
+// controllers implements ControllerInterface
+type controllers struct {
+ *gentype.ClientWithList[*v1alpha1.Controller, *v1alpha1.ControllerList]
+}
+
+// newControllers returns a Controllers
+func newControllers(c *OperatorV1alpha1Client, namespace string) *controllers {
+ return &controllers{
+ gentype.NewClientWithList[*v1alpha1.Controller, *v1alpha1.ControllerList](
+ "controllers",
+ c.RESTClient(),
+ scheme.ParameterCodec,
+ namespace,
+ func() *v1alpha1.Controller { return &v1alpha1.Controller{} },
+ func() *v1alpha1.ControllerList { return &v1alpha1.ControllerList{} }),
+ }
+}
diff --git a/pkg/client/clientset/versioned/typed/operator/v1alpha1/doc.go b/pkg/client/clientset/versioned/typed/operator/v1alpha1/doc.go
new file mode 100644
index 0000000000..df51baa4d4
--- /dev/null
+++ b/pkg/client/clientset/versioned/typed/operator/v1alpha1/doc.go
@@ -0,0 +1,20 @@
+/*
+Copyright The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+// This package has the automatically generated typed clients.
+package v1alpha1
diff --git a/pkg/client/clientset/versioned/typed/operator/v1alpha1/fake/doc.go b/pkg/client/clientset/versioned/typed/operator/v1alpha1/fake/doc.go
new file mode 100644
index 0000000000..16f4439906
--- /dev/null
+++ b/pkg/client/clientset/versioned/typed/operator/v1alpha1/fake/doc.go
@@ -0,0 +1,20 @@
+/*
+Copyright The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+// Package fake has the automatically generated clients.
+package fake
diff --git a/pkg/client/clientset/versioned/typed/operator/v1alpha1/fake/fake_controller.go b/pkg/client/clientset/versioned/typed/operator/v1alpha1/fake/fake_controller.go
new file mode 100644
index 0000000000..32a8c70702
--- /dev/null
+++ b/pkg/client/clientset/versioned/typed/operator/v1alpha1/fake/fake_controller.go
@@ -0,0 +1,147 @@
+/*
+Copyright The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package fake
+
+import (
+ "context"
+
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ labels "k8s.io/apimachinery/pkg/labels"
+ types "k8s.io/apimachinery/pkg/types"
+ watch "k8s.io/apimachinery/pkg/watch"
+ testing "k8s.io/client-go/testing"
+ v1alpha1 "sigs.k8s.io/kwok/pkg/apis/operator/v1alpha1"
+)
+
+// FakeControllers implements ControllerInterface
+type FakeControllers struct {
+ Fake *FakeOperatorV1alpha1
+ ns string
+}
+
+var controllersResource = v1alpha1.SchemeGroupVersion.WithResource("controllers")
+
+var controllersKind = v1alpha1.SchemeGroupVersion.WithKind("Controller")
+
+// Get takes name of the controller, and returns the corresponding controller object, and an error if there is any.
+func (c *FakeControllers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.Controller, err error) {
+ emptyResult := &v1alpha1.Controller{}
+ obj, err := c.Fake.
+ Invokes(testing.NewGetActionWithOptions(controllersResource, c.ns, name, options), emptyResult)
+
+ if obj == nil {
+ return emptyResult, err
+ }
+ return obj.(*v1alpha1.Controller), err
+}
+
+// List takes label and field selectors, and returns the list of Controllers that match those selectors.
+func (c *FakeControllers) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.ControllerList, err error) {
+ emptyResult := &v1alpha1.ControllerList{}
+ obj, err := c.Fake.
+ Invokes(testing.NewListActionWithOptions(controllersResource, controllersKind, c.ns, opts), emptyResult)
+
+ if obj == nil {
+ return emptyResult, err
+ }
+
+ label, _, _ := testing.ExtractFromListOptions(opts)
+ if label == nil {
+ label = labels.Everything()
+ }
+ list := &v1alpha1.ControllerList{ListMeta: obj.(*v1alpha1.ControllerList).ListMeta}
+ for _, item := range obj.(*v1alpha1.ControllerList).Items {
+ if label.Matches(labels.Set(item.Labels)) {
+ list.Items = append(list.Items, item)
+ }
+ }
+ return list, err
+}
+
+// Watch returns a watch.Interface that watches the requested controllers.
+func (c *FakeControllers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
+ return c.Fake.
+ InvokesWatch(testing.NewWatchActionWithOptions(controllersResource, c.ns, opts))
+
+}
+
+// Create takes the representation of a controller and creates it. Returns the server's representation of the controller, and an error, if there is any.
+func (c *FakeControllers) Create(ctx context.Context, controller *v1alpha1.Controller, opts v1.CreateOptions) (result *v1alpha1.Controller, err error) {
+ emptyResult := &v1alpha1.Controller{}
+ obj, err := c.Fake.
+ Invokes(testing.NewCreateActionWithOptions(controllersResource, c.ns, controller, opts), emptyResult)
+
+ if obj == nil {
+ return emptyResult, err
+ }
+ return obj.(*v1alpha1.Controller), err
+}
+
+// Update takes the representation of a controller and updates it. Returns the server's representation of the controller, and an error, if there is any.
+func (c *FakeControllers) Update(ctx context.Context, controller *v1alpha1.Controller, opts v1.UpdateOptions) (result *v1alpha1.Controller, err error) {
+ emptyResult := &v1alpha1.Controller{}
+ obj, err := c.Fake.
+ Invokes(testing.NewUpdateActionWithOptions(controllersResource, c.ns, controller, opts), emptyResult)
+
+ if obj == nil {
+ return emptyResult, err
+ }
+ return obj.(*v1alpha1.Controller), err
+}
+
+// UpdateStatus was generated because the type contains a Status member.
+// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
+func (c *FakeControllers) UpdateStatus(ctx context.Context, controller *v1alpha1.Controller, opts v1.UpdateOptions) (result *v1alpha1.Controller, err error) {
+ emptyResult := &v1alpha1.Controller{}
+ obj, err := c.Fake.
+ Invokes(testing.NewUpdateSubresourceActionWithOptions(controllersResource, "status", c.ns, controller, opts), emptyResult)
+
+ if obj == nil {
+ return emptyResult, err
+ }
+ return obj.(*v1alpha1.Controller), err
+}
+
+// Delete takes name of the controller and deletes it. Returns an error if one occurs.
+func (c *FakeControllers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
+ _, err := c.Fake.
+ Invokes(testing.NewDeleteActionWithOptions(controllersResource, c.ns, name, opts), &v1alpha1.Controller{})
+
+ return err
+}
+
+// DeleteCollection deletes a collection of objects.
+func (c *FakeControllers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
+ action := testing.NewDeleteCollectionActionWithOptions(controllersResource, c.ns, opts, listOpts)
+
+ _, err := c.Fake.Invokes(action, &v1alpha1.ControllerList{})
+ return err
+}
+
+// Patch applies the patch and returns the patched controller.
+func (c *FakeControllers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Controller, err error) {
+ emptyResult := &v1alpha1.Controller{}
+ obj, err := c.Fake.
+ Invokes(testing.NewPatchSubresourceActionWithOptions(controllersResource, c.ns, name, pt, data, opts, subresources...), emptyResult)
+
+ if obj == nil {
+ return emptyResult, err
+ }
+ return obj.(*v1alpha1.Controller), err
+}
diff --git a/pkg/client/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go b/pkg/client/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go
new file mode 100644
index 0000000000..94999820f9
--- /dev/null
+++ b/pkg/client/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go
@@ -0,0 +1,40 @@
+/*
+Copyright The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package fake
+
+import (
+ rest "k8s.io/client-go/rest"
+ testing "k8s.io/client-go/testing"
+ v1alpha1 "sigs.k8s.io/kwok/pkg/client/clientset/versioned/typed/operator/v1alpha1"
+)
+
+type FakeOperatorV1alpha1 struct {
+ *testing.Fake
+}
+
+func (c *FakeOperatorV1alpha1) Controllers(namespace string) v1alpha1.ControllerInterface {
+ return &FakeControllers{c, namespace}
+}
+
+// RESTClient returns a RESTClient that is used to communicate
+// with API server by this client implementation.
+func (c *FakeOperatorV1alpha1) RESTClient() rest.Interface {
+ var ret *rest.RESTClient
+ return ret
+}
diff --git a/pkg/client/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go b/pkg/client/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go
new file mode 100644
index 0000000000..54477870f3
--- /dev/null
+++ b/pkg/client/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go
@@ -0,0 +1,21 @@
+/*
+Copyright The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package v1alpha1
+
+type ControllerExpansion interface{}
diff --git a/pkg/client/clientset/versioned/typed/operator/v1alpha1/operator_client.go b/pkg/client/clientset/versioned/typed/operator/v1alpha1/operator_client.go
new file mode 100644
index 0000000000..cd90ba051c
--- /dev/null
+++ b/pkg/client/clientset/versioned/typed/operator/v1alpha1/operator_client.go
@@ -0,0 +1,107 @@
+/*
+Copyright The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ "net/http"
+
+ rest "k8s.io/client-go/rest"
+ v1alpha1 "sigs.k8s.io/kwok/pkg/apis/operator/v1alpha1"
+ "sigs.k8s.io/kwok/pkg/client/clientset/versioned/scheme"
+)
+
+type OperatorV1alpha1Interface interface {
+ RESTClient() rest.Interface
+ ControllersGetter
+}
+
+// OperatorV1alpha1Client is used to interact with features provided by the operator.kwok.x-k8s.io group.
+type OperatorV1alpha1Client struct {
+ restClient rest.Interface
+}
+
+func (c *OperatorV1alpha1Client) Controllers(namespace string) ControllerInterface {
+ return newControllers(c, namespace)
+}
+
+// NewForConfig creates a new OperatorV1alpha1Client for the given config.
+// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
+// where httpClient was generated with rest.HTTPClientFor(c).
+func NewForConfig(c *rest.Config) (*OperatorV1alpha1Client, error) {
+ config := *c
+ if err := setConfigDefaults(&config); err != nil {
+ return nil, err
+ }
+ httpClient, err := rest.HTTPClientFor(&config)
+ if err != nil {
+ return nil, err
+ }
+ return NewForConfigAndClient(&config, httpClient)
+}
+
+// NewForConfigAndClient creates a new OperatorV1alpha1Client for the given config and http client.
+// Note the http client provided takes precedence over the configured transport values.
+func NewForConfigAndClient(c *rest.Config, h *http.Client) (*OperatorV1alpha1Client, error) {
+ config := *c
+ if err := setConfigDefaults(&config); err != nil {
+ return nil, err
+ }
+ client, err := rest.RESTClientForConfigAndClient(&config, h)
+ if err != nil {
+ return nil, err
+ }
+ return &OperatorV1alpha1Client{client}, nil
+}
+
+// NewForConfigOrDie creates a new OperatorV1alpha1Client for the given config and
+// panics if there is an error in the config.
+func NewForConfigOrDie(c *rest.Config) *OperatorV1alpha1Client {
+ client, err := NewForConfig(c)
+ if err != nil {
+ panic(err)
+ }
+ return client
+}
+
+// New creates a new OperatorV1alpha1Client for the given RESTClient.
+func New(c rest.Interface) *OperatorV1alpha1Client {
+ return &OperatorV1alpha1Client{c}
+}
+
+func setConfigDefaults(config *rest.Config) error {
+ gv := v1alpha1.SchemeGroupVersion
+ config.GroupVersion = &gv
+ config.APIPath = "/apis"
+ config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
+
+ if config.UserAgent == "" {
+ config.UserAgent = rest.DefaultKubernetesUserAgent()
+ }
+
+ return nil
+}
+
+// RESTClient returns a RESTClient that is used to communicate
+// with API server by this client implementation.
+func (c *OperatorV1alpha1Client) RESTClient() rest.Interface {
+ if c == nil {
+ return nil
+ }
+ return c.restClient
+}
diff --git a/pkg/operator/cmd/root.go b/pkg/operator/cmd/root.go
new file mode 100644
index 0000000000..8a7f2fd8eb
--- /dev/null
+++ b/pkg/operator/cmd/root.go
@@ -0,0 +1,125 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package cmd defines a root command for the kwok.
+package cmd
+
+import (
+ "context"
+ "os"
+
+ "github.com/spf13/cobra"
+ "k8s.io/client-go/kubernetes"
+
+ "sigs.k8s.io/kwok/pkg/client/clientset/versioned"
+ "sigs.k8s.io/kwok/pkg/log"
+ "sigs.k8s.io/kwok/pkg/operator/controllers"
+ "sigs.k8s.io/kwok/pkg/utils/client"
+ "sigs.k8s.io/kwok/pkg/utils/kubeconfig"
+ "sigs.k8s.io/kwok/pkg/utils/path"
+ "sigs.k8s.io/kwok/pkg/utils/version"
+)
+
+type flagpole struct {
+ Kubeconfig string
+ Master string
+
+ SourceControllerNamespace string
+ SourceControllerName string
+}
+
+// NewCommand returns a new cobra.Command for root
+func NewCommand(ctx context.Context) *cobra.Command {
+ flags := &flagpole{}
+ cmd := &cobra.Command{
+ Args: cobra.NoArgs,
+ Use: "kwok-operator",
+ Short: "kwok-operator is a operator for kwok-controller",
+ SilenceUsage: true,
+ SilenceErrors: true,
+ Version: version.DisplayVersion(),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return runE(cmd.Context(), flags)
+ },
+ }
+
+ flags.Kubeconfig = path.RelFromHome(kubeconfig.GetRecommendedKubeconfigPath())
+
+ cmd.Flags().StringVar(&flags.Kubeconfig, "kubeconfig", flags.Kubeconfig, "Path to the kubeconfig file to use")
+ cmd.Flags().StringVar(&flags.Master, "master", flags.Master, "The address of the Kubernetes API server (overrides any value in kubeconfig).")
+
+ cmd.Flags().StringVar(&flags.SourceControllerName, "controller-name", "kwok-controller", "The name of the controller to use for the source controller")
+ cmd.Flags().StringVar(&flags.SourceControllerNamespace, "controller-namespace", "kube-system", "The namespace of the controller to use for the source controller")
+ return cmd
+}
+
+func runE(ctx context.Context, flags *flagpole) error {
+ logger := log.FromContext(ctx)
+
+ if flags.Kubeconfig != "" {
+ var err error
+ flags.Kubeconfig, err = path.Expand(flags.Kubeconfig)
+ if err != nil {
+ return err
+ }
+ f, err := os.Stat(flags.Kubeconfig)
+ if err != nil || f.IsDir() {
+ logger.Warn("Failed to get kubeconfig file or it is a directory", "kubeconfig", flags.Kubeconfig)
+ flags.Kubeconfig = ""
+ }
+ }
+
+ if flags.Kubeconfig == "" && flags.Master == "" {
+ logger.Warn("Neither --kubeconfig nor --master was specified")
+ logger.Info("Using the inClusterConfig")
+ }
+ clientset, err := client.NewClientset(flags.Master, flags.Kubeconfig)
+ if err != nil {
+ return err
+ }
+
+ restConfig, err := clientset.ToRESTConfig()
+ if err != nil {
+ return err
+ }
+
+ typedClient, err := kubernetes.NewForConfig(restConfig)
+ if err != nil {
+ return err
+ }
+ typedKwokClient, err := versioned.NewForConfig(restConfig)
+ if err != nil {
+ return err
+ }
+
+ ctr, err := controllers.NewController(controllers.ControllerConfig{
+ TypedClient: typedClient,
+ TypedKwokClient: typedKwokClient,
+ SourceNamespace: flags.SourceControllerNamespace,
+ SourceName: flags.SourceControllerName,
+ })
+ if err != nil {
+ return err
+ }
+
+ err = ctr.Start(ctx)
+ if err != nil {
+ return err
+ }
+
+ <-ctx.Done()
+ return nil
+}
diff --git a/pkg/operator/controllers/controller.go b/pkg/operator/controllers/controller.go
new file mode 100644
index 0000000000..bf9650925c
--- /dev/null
+++ b/pkg/operator/controllers/controller.go
@@ -0,0 +1,202 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package controllers
+
+import (
+ "context"
+ "fmt"
+ "reflect"
+
+ autoscalingv1 "k8s.io/api/autoscaling/v1"
+ corev1 "k8s.io/api/core/v1"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes"
+
+ "sigs.k8s.io/kwok/pkg/apis/operator/v1alpha1"
+ "sigs.k8s.io/kwok/pkg/client/clientset/versioned"
+ "sigs.k8s.io/kwok/pkg/log"
+ "sigs.k8s.io/kwok/pkg/utils/format"
+ "sigs.k8s.io/kwok/pkg/utils/informer"
+ "sigs.k8s.io/kwok/pkg/utils/sets"
+)
+
+type Controller struct {
+ typedClient kubernetes.Interface
+ typedKwokClient versioned.Interface
+ sourceNamespace string
+ sourceName string
+
+ podTemplate *corev1.PodTemplateSpec
+ controllersInformer *informer.Informer[*v1alpha1.Controller, *v1alpha1.ControllerList]
+ controllersCacheGetter informer.Getter[*v1alpha1.Controller]
+ controllersChan chan informer.Event[*v1alpha1.Controller]
+}
+
+// ControllerConfig is the configuration for the controller
+type ControllerConfig struct {
+ TypedClient kubernetes.Interface
+ TypedKwokClient versioned.Interface
+
+ SourceNamespace string
+ SourceName string
+}
+
+func NewController(conf ControllerConfig) (*Controller, error) {
+ c := &Controller{
+ typedClient: conf.TypedClient,
+ typedKwokClient: conf.TypedKwokClient,
+ sourceNamespace: conf.SourceNamespace,
+ sourceName: conf.SourceName,
+ }
+
+ return c, nil
+}
+
+func (c *Controller) Start(ctx context.Context) error {
+ c.controllersInformer = informer.NewInformer[*v1alpha1.Controller, *v1alpha1.ControllerList](c.typedKwokClient.OperatorV1alpha1().Controllers(""))
+ c.controllersChan = make(chan informer.Event[*v1alpha1.Controller], 1)
+ controllersCacheGetter, err := c.controllersInformer.WatchWithCache(ctx, informer.Option{}, c.controllersChan)
+ if err != nil {
+ return err
+ }
+ c.controllersCacheGetter = controllersCacheGetter
+
+ go c.syncWorker(ctx)
+ return nil
+}
+
+func (c *Controller) scaleDeployment(ctx context.Context, replicas int32) error {
+ deploy, err := c.typedClient.AppsV1().
+ Deployments(c.sourceNamespace).
+ Get(ctx, c.sourceName, metav1.GetOptions{})
+ if err != nil {
+ return fmt.Errorf("getting deployment: %w", err)
+ }
+ c.podTemplate = &deploy.Spec.Template
+
+ if *deploy.Spec.Replicas == replicas {
+ return nil
+ }
+
+ _, err = c.typedClient.AppsV1().
+ Deployments(c.sourceNamespace).
+ UpdateScale(ctx, c.sourceName, &autoscalingv1.Scale{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: c.sourceName,
+ },
+ Spec: autoscalingv1.ScaleSpec{
+ Replicas: replicas,
+ },
+ }, metav1.UpdateOptions{})
+ if err != nil {
+ return fmt.Errorf("updating deployment scale: %w", err)
+ }
+ return nil
+}
+
+func (c *Controller) syncWorker(ctx context.Context) {
+ s := sets.Sets[log.ObjectRef]{}
+
+ logger := log.FromContext(ctx)
+ for {
+ select {
+ case <-ctx.Done():
+ logger.Debug("Stop controller sync worker")
+ return
+ case event := <-c.controllersChan:
+ cc := event.Object
+
+ p := c.typedClient.CoreV1().
+ Pods(cc.Namespace)
+
+ if event.Type == informer.Deleted {
+ err := p.Delete(ctx, cc.GetName(), metav1.DeleteOptions{})
+ if err != nil {
+ logger.Error("can't delete the origin pod", err)
+ }
+ s.Delete(log.KObj(cc))
+ if s.Len() == 0 {
+ err := c.scaleDeployment(ctx, 1)
+ if err != nil {
+ logger.Error("can't sync deployment", err)
+ continue
+ }
+ }
+ continue
+ }
+ if s.Len() == 0 {
+ err := c.scaleDeployment(ctx, 0)
+ if err != nil {
+ logger.Error("can't sync deployment", err)
+ continue
+ }
+ }
+ s.Insert(log.KObj(cc))
+
+ pod := controllerPod(cc, c.podTemplate.Spec)
+
+ oriPod, err := p.Get(ctx, cc.GetName(), metav1.GetOptions{})
+ if err != nil {
+ if !apierrors.IsNotFound(err) {
+ logger.Error("can't get the origin pod", err)
+ continue
+ }
+ } else {
+ if reflect.DeepEqual(pod.Spec, oriPod.Spec) {
+ continue
+ }
+
+ err = p.Delete(ctx, cc.GetName(), metav1.DeleteOptions{
+ GracePeriodSeconds: format.Ptr[int64](0),
+ })
+ if err != nil {
+ logger.Error("can't delete the origin pod", err)
+ continue
+ }
+ }
+
+ _, err = p.Create(ctx, pod, metav1.CreateOptions{})
+ if err != nil {
+ logger.Error("can't create the pod", err)
+ continue
+ }
+ }
+ }
+}
+
+func controllerPod(c *v1alpha1.Controller, ps corev1.PodSpec) *corev1.Pod {
+ pod := corev1.Pod{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: c.Name,
+ Namespace: c.Namespace,
+ Labels: c.Labels,
+ Annotations: c.Annotations,
+ OwnerReferences: []metav1.OwnerReference{
+ {
+ APIVersion: v1alpha1.GroupVersion.String(),
+ Kind: v1alpha1.ControllerKind,
+ Name: c.Name,
+ UID: c.ObjectMeta.UID,
+ Controller: format.Ptr(true),
+ },
+ },
+ },
+ Spec: ps,
+ }
+ return &pod
+}
diff --git a/pkg/operator/controllers/doc.go b/pkg/operator/controllers/doc.go
new file mode 100644
index 0000000000..ee82b3adf4
--- /dev/null
+++ b/pkg/operator/controllers/doc.go
@@ -0,0 +1,18 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package controllers is the package that contains the operator logic.
+package controllers
diff --git a/site/content/en/docs/generated/apis.md b/site/content/en/docs/generated/apis.md
index 259a5c3d45..404fa6939d 100644
--- a/site/content/en/docs/generated/apis.md
+++ b/site/content/en/docs/generated/apis.md
@@ -14,6 +14,9 @@ bookToc: false
kwok.x-k8s.io/v1alpha1
+
+operator.kwok.x-k8s.io/v1alpha1
+
action.kwok.x-k8s.io/v1alpha1
@@ -1799,6 +1802,109 @@ StageStatus
+
+operator.kwok.x-k8s.io/v1alpha1
+ #
+
+
+
Package v1alpha1 implements the v1alpha1 apiVersion of kwok’s operator
+
+Resource Types:
+
+
+Controller
+ #
+
+
+
Controller provides controller configuration for a single pod.
+
+
References
#
@@ -6731,3 +6837,261 @@ StageStatus
+
+Condition
+ #
+
+
+Appears on:
+ControllerStatus
+
+
+
Condition contains details for one aspect of the current state of this API Resource.
+
+
+
+
+Field |
+Description |
+
+
+
+
+
+type
+
+string
+
+ |
+
+ Type of condition in CamelCase or in foo.example.com/CamelCase.
+Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be
+useful (see .node.status.conditions), the ability to deconflict is important.
+The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
+ |
+
+
+
+status
+
+
+ConditionStatus
+
+
+ |
+
+ Status of the condition
+ |
+
+
+
+lastTransitionTime
+
+
+Kubernetes meta/v1.Time
+
+
+ |
+
+ LastTransitionTime is the last time the condition transitioned from one status to another.
+This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
+ |
+
+
+
+reason
+
+string
+
+ |
+
+ Reason contains a programmatic identifier indicating the reason for the condition’s last transition.
+Producers of specific condition types may define expected values and meanings for this field,
+and whether the values are considered a guaranteed API.
+The value should be a CamelCase string.
+This field may not be empty.
+ |
+
+
+
+message
+
+string
+
+ |
+
+ Message is a human readable message indicating details about the transition.
+This may be an empty string.
+ |
+
+
+
+
+ConditionStatus
+(string
alias)
+ #
+
+
+Appears on:
+Condition
+
+
+
ConditionStatus is the status of a condition.
+
+
+
+
+Value |
+Description |
+
+
+
+
+"False" |
+ConditionFalse means a resource is not in the condition.
+ |
+
+
+"True" |
+ConditionTrue means a resource is in the condition.
+ |
+
+
+"Unknown" |
+ConditionUnknown means kubernetes can’t decide if a resource is in the condition or not.
+ |
+
+
+
+
+ControllerSpec
+ #
+
+
+Appears on:
+Controller
+
+
+
ControllerSpec holds spec for controller.
+
+
+
+ControllerStatus
+ #
+
+
+Appears on:
+Controller
+
+
+
ControllerStatus holds status for controller
+
+
+
+
+Field |
+Description |
+
+
+
+
+
+conditions
+
+
+[]Condition
+
+
+ |
+
+ Conditions holds conditions for controller
+ |
+
+
+
+
+TargetResourceRef
+ #
+
+
+Appears on:
+ControllerSpec
+
+
+
TargetResourceRef specifies the kind and version of the resource.
+
+
+
+
+Field |
+Description |
+
+
+
+
+
+apiGroup
+
+string
+
+ |
+
+ APIGroup of the referent.
+ |
+
+
+
+kind
+
+string
+
+ |
+
+ Kind of the referent.
+ |
+
+
+
+namespace
+
+string
+
+ |
+
+ Namespace of the resource
+ |
+
+
+
+name
+
+string
+
+ |
+
+ Name of the resource
+ |
+
+
+
diff --git a/test/release/testdata/build-with-push-bucket-staging.txt b/test/release/testdata/build-with-push-bucket-staging.txt
index 551711a2eb..87eb273b46 100644
--- a/test/release/testdata/build-with-push-bucket-staging.txt
+++ b/test/release/testdata/build-with-push-bucket-staging.txt
@@ -2,3 +2,5 @@ GOOS= GOARCH= go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Versio
gsutil cp -P ./bin///kwok bucket/releases/staging-prefix-/bin///kwok
GOOS= GOARCH= go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin///kwokctl ./cmd/kwokctl
gsutil cp -P ./bin///kwokctl bucket/releases/staging-prefix-/bin///kwokctl
+GOOS= GOARCH= go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin///kwok-operator ./cmd/kwok-operator
+gsutil cp -P ./bin///kwok-operator bucket/releases/staging-prefix-/bin///kwok-operator
diff --git a/test/release/testdata/build-with-push-bucket.txt b/test/release/testdata/build-with-push-bucket.txt
index 4089b26124..11697231b3 100644
--- a/test/release/testdata/build-with-push-bucket.txt
+++ b/test/release/testdata/build-with-push-bucket.txt
@@ -2,3 +2,5 @@ GOOS= GOARCH= go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Versio
gsutil cp -P ./bin///kwok bucket/releases//bin///kwok
GOOS= GOARCH= go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin///kwokctl ./cmd/kwokctl
gsutil cp -P ./bin///kwokctl bucket/releases//bin///kwokctl
+GOOS= GOARCH= go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin///kwok-operator ./cmd/kwok-operator
+gsutil cp -P ./bin///kwok-operator bucket/releases//bin///kwok-operator
diff --git a/test/release/testdata/build-with-push-ghrelease.txt b/test/release/testdata/build-with-push-ghrelease.txt
index d2e9bf3074..7b9b9acd2a 100644
--- a/test/release/testdata/build-with-push-ghrelease.txt
+++ b/test/release/testdata/build-with-push-ghrelease.txt
@@ -4,3 +4,6 @@ gh -R ghrelease release upload kwok--
GOOS= GOARCH= go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin///kwokctl ./cmd/kwokctl
cp ./bin///kwokctl kwokctl--
gh -R ghrelease release upload kwokctl--
+GOOS= GOARCH= go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin///kwok-operator ./cmd/kwok-operator
+cp ./bin///kwok-operator kwok-operator--
+gh -R ghrelease release upload kwok-operator--
diff --git a/test/release/testdata/build.txt b/test/release/testdata/build.txt
index 032e578fde..4df37d53fc 100644
--- a/test/release/testdata/build.txt
+++ b/test/release/testdata/build.txt
@@ -1,2 +1,3 @@
GOOS= GOARCH= go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin///kwok ./cmd/kwok
GOOS= GOARCH= go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin///kwokctl ./cmd/kwokctl
+GOOS= GOARCH= go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin///kwok-operator ./cmd/kwok-operator
diff --git a/test/release/testdata/cross-build-with-push-bucket-staging.txt b/test/release/testdata/cross-build-with-push-bucket-staging.txt
index 62fc979dc7..b7ca3e5344 100644
--- a/test/release/testdata/cross-build-with-push-bucket-staging.txt
+++ b/test/release/testdata/cross-build-with-push-bucket-staging.txt
@@ -2,23 +2,35 @@ GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Versio
gsutil cp -P ./bin/linux/amd64/kwok bucket/releases/staging-prefix-/bin/linux/amd64/kwok
GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/amd64/kwokctl ./cmd/kwokctl
gsutil cp -P ./bin/linux/amd64/kwokctl bucket/releases/staging-prefix-/bin/linux/amd64/kwokctl
+GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/amd64/kwok-operator ./cmd/kwok-operator
+gsutil cp -P ./bin/linux/amd64/kwok-operator bucket/releases/staging-prefix-/bin/linux/amd64/kwok-operator
GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/arm64/kwok ./cmd/kwok
gsutil cp -P ./bin/linux/arm64/kwok bucket/releases/staging-prefix-/bin/linux/arm64/kwok
GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/arm64/kwokctl ./cmd/kwokctl
gsutil cp -P ./bin/linux/arm64/kwokctl bucket/releases/staging-prefix-/bin/linux/arm64/kwokctl
+GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/arm64/kwok-operator ./cmd/kwok-operator
+gsutil cp -P ./bin/linux/arm64/kwok-operator bucket/releases/staging-prefix-/bin/linux/arm64/kwok-operator
GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/amd64/kwok ./cmd/kwok
gsutil cp -P ./bin/darwin/amd64/kwok bucket/releases/staging-prefix-/bin/darwin/amd64/kwok
GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/amd64/kwokctl ./cmd/kwokctl
gsutil cp -P ./bin/darwin/amd64/kwokctl bucket/releases/staging-prefix-/bin/darwin/amd64/kwokctl
+GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/amd64/kwok-operator ./cmd/kwok-operator
+gsutil cp -P ./bin/darwin/amd64/kwok-operator bucket/releases/staging-prefix-/bin/darwin/amd64/kwok-operator
GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/arm64/kwok ./cmd/kwok
gsutil cp -P ./bin/darwin/arm64/kwok bucket/releases/staging-prefix-/bin/darwin/arm64/kwok
GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/arm64/kwokctl ./cmd/kwokctl
gsutil cp -P ./bin/darwin/arm64/kwokctl bucket/releases/staging-prefix-/bin/darwin/arm64/kwokctl
+GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/arm64/kwok-operator ./cmd/kwok-operator
+gsutil cp -P ./bin/darwin/arm64/kwok-operator bucket/releases/staging-prefix-/bin/darwin/arm64/kwok-operator
GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/amd64/kwok.exe ./cmd/kwok
gsutil cp -P ./bin/windows/amd64/kwok.exe bucket/releases/staging-prefix-/bin/windows/amd64/kwok.exe
GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/amd64/kwokctl.exe ./cmd/kwokctl
gsutil cp -P ./bin/windows/amd64/kwokctl.exe bucket/releases/staging-prefix-/bin/windows/amd64/kwokctl.exe
+GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/amd64/kwok-operator.exe ./cmd/kwok-operator
+gsutil cp -P ./bin/windows/amd64/kwok-operator.exe bucket/releases/staging-prefix-/bin/windows/amd64/kwok-operator.exe
GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/arm64/kwok.exe ./cmd/kwok
gsutil cp -P ./bin/windows/arm64/kwok.exe bucket/releases/staging-prefix-/bin/windows/arm64/kwok.exe
GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/arm64/kwokctl.exe ./cmd/kwokctl
gsutil cp -P ./bin/windows/arm64/kwokctl.exe bucket/releases/staging-prefix-/bin/windows/arm64/kwokctl.exe
+GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/arm64/kwok-operator.exe ./cmd/kwok-operator
+gsutil cp -P ./bin/windows/arm64/kwok-operator.exe bucket/releases/staging-prefix-/bin/windows/arm64/kwok-operator.exe
diff --git a/test/release/testdata/cross-build-with-push-bucket.txt b/test/release/testdata/cross-build-with-push-bucket.txt
index 5a51d338d2..2b4eb58f4b 100644
--- a/test/release/testdata/cross-build-with-push-bucket.txt
+++ b/test/release/testdata/cross-build-with-push-bucket.txt
@@ -2,23 +2,35 @@ GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Versio
gsutil cp -P ./bin/linux/amd64/kwok bucket/releases//bin/linux/amd64/kwok
GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/amd64/kwokctl ./cmd/kwokctl
gsutil cp -P ./bin/linux/amd64/kwokctl bucket/releases//bin/linux/amd64/kwokctl
+GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/amd64/kwok-operator ./cmd/kwok-operator
+gsutil cp -P ./bin/linux/amd64/kwok-operator bucket/releases//bin/linux/amd64/kwok-operator
GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/arm64/kwok ./cmd/kwok
gsutil cp -P ./bin/linux/arm64/kwok bucket/releases//bin/linux/arm64/kwok
GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/arm64/kwokctl ./cmd/kwokctl
gsutil cp -P ./bin/linux/arm64/kwokctl bucket/releases//bin/linux/arm64/kwokctl
+GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/arm64/kwok-operator ./cmd/kwok-operator
+gsutil cp -P ./bin/linux/arm64/kwok-operator bucket/releases//bin/linux/arm64/kwok-operator
GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/amd64/kwok ./cmd/kwok
gsutil cp -P ./bin/darwin/amd64/kwok bucket/releases//bin/darwin/amd64/kwok
GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/amd64/kwokctl ./cmd/kwokctl
gsutil cp -P ./bin/darwin/amd64/kwokctl bucket/releases//bin/darwin/amd64/kwokctl
+GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/amd64/kwok-operator ./cmd/kwok-operator
+gsutil cp -P ./bin/darwin/amd64/kwok-operator bucket/releases//bin/darwin/amd64/kwok-operator
GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/arm64/kwok ./cmd/kwok
gsutil cp -P ./bin/darwin/arm64/kwok bucket/releases//bin/darwin/arm64/kwok
GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/arm64/kwokctl ./cmd/kwokctl
gsutil cp -P ./bin/darwin/arm64/kwokctl bucket/releases//bin/darwin/arm64/kwokctl
+GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/arm64/kwok-operator ./cmd/kwok-operator
+gsutil cp -P ./bin/darwin/arm64/kwok-operator bucket/releases//bin/darwin/arm64/kwok-operator
GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/amd64/kwok.exe ./cmd/kwok
gsutil cp -P ./bin/windows/amd64/kwok.exe bucket/releases//bin/windows/amd64/kwok.exe
GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/amd64/kwokctl.exe ./cmd/kwokctl
gsutil cp -P ./bin/windows/amd64/kwokctl.exe bucket/releases//bin/windows/amd64/kwokctl.exe
+GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/amd64/kwok-operator.exe ./cmd/kwok-operator
+gsutil cp -P ./bin/windows/amd64/kwok-operator.exe bucket/releases//bin/windows/amd64/kwok-operator.exe
GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/arm64/kwok.exe ./cmd/kwok
gsutil cp -P ./bin/windows/arm64/kwok.exe bucket/releases//bin/windows/arm64/kwok.exe
GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/arm64/kwokctl.exe ./cmd/kwokctl
gsutil cp -P ./bin/windows/arm64/kwokctl.exe bucket/releases//bin/windows/arm64/kwokctl.exe
+GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/arm64/kwok-operator.exe ./cmd/kwok-operator
+gsutil cp -P ./bin/windows/arm64/kwok-operator.exe bucket/releases//bin/windows/arm64/kwok-operator.exe
diff --git a/test/release/testdata/cross-build-with-push-ghrelease.txt b/test/release/testdata/cross-build-with-push-ghrelease.txt
index 107c867609..438849d79c 100644
--- a/test/release/testdata/cross-build-with-push-ghrelease.txt
+++ b/test/release/testdata/cross-build-with-push-ghrelease.txt
@@ -4,33 +4,51 @@ gh -R ghrelease release upload kwok-linux-amd64
GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/amd64/kwokctl ./cmd/kwokctl
cp ./bin/linux/amd64/kwokctl kwokctl-linux-amd64
gh -R ghrelease release upload kwokctl-linux-amd64
+GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/amd64/kwok-operator ./cmd/kwok-operator
+cp ./bin/linux/amd64/kwok-operator kwok-operator-linux-amd64
+gh -R ghrelease release upload kwok-operator-linux-amd64
GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/arm64/kwok ./cmd/kwok
cp ./bin/linux/arm64/kwok kwok-linux-arm64
gh -R ghrelease release upload kwok-linux-arm64
GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/arm64/kwokctl ./cmd/kwokctl
cp ./bin/linux/arm64/kwokctl kwokctl-linux-arm64
gh -R ghrelease release upload kwokctl-linux-arm64
+GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/arm64/kwok-operator ./cmd/kwok-operator
+cp ./bin/linux/arm64/kwok-operator kwok-operator-linux-arm64
+gh -R ghrelease release upload kwok-operator-linux-arm64
GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/amd64/kwok ./cmd/kwok
cp ./bin/darwin/amd64/kwok kwok-darwin-amd64
gh -R ghrelease release upload kwok-darwin-amd64
GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/amd64/kwokctl ./cmd/kwokctl
cp ./bin/darwin/amd64/kwokctl kwokctl-darwin-amd64
gh -R ghrelease release upload kwokctl-darwin-amd64
+GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/amd64/kwok-operator ./cmd/kwok-operator
+cp ./bin/darwin/amd64/kwok-operator kwok-operator-darwin-amd64
+gh -R ghrelease release upload kwok-operator-darwin-amd64
GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/arm64/kwok ./cmd/kwok
cp ./bin/darwin/arm64/kwok kwok-darwin-arm64
gh -R ghrelease release upload kwok-darwin-arm64
GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/arm64/kwokctl ./cmd/kwokctl
cp ./bin/darwin/arm64/kwokctl kwokctl-darwin-arm64
gh -R ghrelease release upload kwokctl-darwin-arm64
+GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/arm64/kwok-operator ./cmd/kwok-operator
+cp ./bin/darwin/arm64/kwok-operator kwok-operator-darwin-arm64
+gh -R ghrelease release upload kwok-operator-darwin-arm64
GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/amd64/kwok.exe ./cmd/kwok
cp ./bin/windows/amd64/kwok.exe kwok-windows-amd64.exe
gh -R ghrelease release upload kwok-windows-amd64.exe
GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/amd64/kwokctl.exe ./cmd/kwokctl
cp ./bin/windows/amd64/kwokctl.exe kwokctl-windows-amd64.exe
gh -R ghrelease release upload kwokctl-windows-amd64.exe
+GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/amd64/kwok-operator.exe ./cmd/kwok-operator
+cp ./bin/windows/amd64/kwok-operator.exe kwok-operator-windows-amd64.exe
+gh -R ghrelease release upload kwok-operator-windows-amd64.exe
GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/arm64/kwok.exe ./cmd/kwok
cp ./bin/windows/arm64/kwok.exe kwok-windows-arm64.exe
gh -R ghrelease release upload kwok-windows-arm64.exe
GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/arm64/kwokctl.exe ./cmd/kwokctl
cp ./bin/windows/arm64/kwokctl.exe kwokctl-windows-arm64.exe
gh -R ghrelease release upload kwokctl-windows-arm64.exe
+GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/arm64/kwok-operator.exe ./cmd/kwok-operator
+cp ./bin/windows/arm64/kwok-operator.exe kwok-operator-windows-arm64.exe
+gh -R ghrelease release upload kwok-operator-windows-arm64.exe
diff --git a/test/release/testdata/cross-build.txt b/test/release/testdata/cross-build.txt
index 05c3c614eb..61c65c4f99 100644
--- a/test/release/testdata/cross-build.txt
+++ b/test/release/testdata/cross-build.txt
@@ -1,12 +1,18 @@
GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/amd64/kwok ./cmd/kwok
GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/amd64/kwokctl ./cmd/kwokctl
+GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/amd64/kwok-operator ./cmd/kwok-operator
GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/arm64/kwok ./cmd/kwok
GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/arm64/kwokctl ./cmd/kwokctl
+GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/arm64/kwok-operator ./cmd/kwok-operator
GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/amd64/kwok ./cmd/kwok
GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/amd64/kwokctl ./cmd/kwokctl
+GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/amd64/kwok-operator ./cmd/kwok-operator
GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/arm64/kwok ./cmd/kwok
GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/arm64/kwokctl ./cmd/kwokctl
+GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/arm64/kwok-operator ./cmd/kwok-operator
GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/amd64/kwok.exe ./cmd/kwok
GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/amd64/kwokctl.exe ./cmd/kwokctl
+GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/amd64/kwok-operator.exe ./cmd/kwok-operator
GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/arm64/kwok.exe ./cmd/kwok
GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/arm64/kwokctl.exe ./cmd/kwokctl
+GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version= -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.31.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/arm64/kwok-operator.exe ./cmd/kwok-operator