feat: scaffold job execution API #1
@@ -0,0 +1,11 @@
|
|||||||
|
# This file configures golangci-lint with module plugins.
|
||||||
|
# When you run 'make lint', it will automatically build a custom golangci-lint binary
|
||||||
|
# with all the plugins listed below.
|
||||||
|
#
|
||||||
|
# See: https://golangci-lint.run/plugins/module-plugins/
|
||||||
|
version: v2.13.1
|
||||||
|
plugins:
|
||||||
|
# logcheck validates structured logging calls and parameters (e.g., balanced key-value pairs)
|
||||||
|
- module: "sigs.k8s.io/logtools"
|
||||||
|
import: "sigs.k8s.io/logtools/logcheck/gclplugin"
|
||||||
|
version: latest
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file
|
||||||
|
# Ignore everything by default and re-include only needed files
|
||||||
|
**
|
||||||
|
|
||||||
|
# Re-include Go source files (but not *_test.go)
|
||||||
|
# If you use Podman, re-include your source directories by name,
|
||||||
|
# such as !cmd, !api, and !internal.
|
||||||
|
# See https://github.com/containers/buildah/issues/6417
|
||||||
|
!**/*.go
|
||||||
|
**/*_test.go
|
||||||
|
|
||||||
|
# Re-include Go module files
|
||||||
|
!go.mod
|
||||||
|
!go.sum
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
name: Verify
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: [self-hosted, pod]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Verify generated files and tests
|
||||||
|
run: |
|
||||||
|
make test
|
||||||
|
git diff --exit-code
|
||||||
|
|
||||||
|
lint:
|
||||||
|
runs-on: [self-hosted, pod]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: make lint
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
/bin/
|
/bin/
|
||||||
/dist/
|
/dist/
|
||||||
/coverage/
|
/coverage/
|
||||||
|
/cover.out
|
||||||
|
|
||||||
# Local configuration and credentials
|
# Local configuration and credentials
|
||||||
.env
|
.env
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
version: "2"
|
||||||
|
run:
|
||||||
|
allow-parallel-runners: true
|
||||||
|
linters:
|
||||||
|
default: none
|
||||||
|
enable:
|
||||||
|
- copyloopvar
|
||||||
|
- depguard
|
||||||
|
- dupl
|
||||||
|
- errcheck
|
||||||
|
- ginkgolinter
|
||||||
|
- goconst
|
||||||
|
- gocyclo
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- lll
|
||||||
|
- modernize
|
||||||
|
- misspell
|
||||||
|
- nakedret
|
||||||
|
- prealloc
|
||||||
|
- revive
|
||||||
|
- staticcheck
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
- unused
|
||||||
|
- logcheck
|
||||||
|
settings:
|
||||||
|
custom:
|
||||||
|
logcheck:
|
||||||
|
type: "module"
|
||||||
|
description: Checks Go logging calls for Kubernetes logging conventions.
|
||||||
|
depguard:
|
||||||
|
rules:
|
||||||
|
forbid-sort-pkg:
|
||||||
|
deny:
|
||||||
|
- pkg: sort
|
||||||
|
desc: Should be replaced with slices package
|
||||||
|
revive:
|
||||||
|
rules:
|
||||||
|
- name: comment-spacings
|
||||||
|
- name: import-shadowing
|
||||||
|
modernize:
|
||||||
|
disable:
|
||||||
|
- omitzero
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
rules:
|
||||||
|
- linters:
|
||||||
|
- lll
|
||||||
|
path: api/*
|
||||||
|
- linters:
|
||||||
|
- dupl
|
||||||
|
- lll
|
||||||
|
path: internal/*
|
||||||
|
paths:
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
|
formatters:
|
||||||
|
enable:
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
paths:
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
@@ -3,6 +3,9 @@
|
|||||||
- 本仓库是 ddupan.top homelab 的内部基础设施控制平面,不以通用发行版为初期目标。
|
- 本仓库是 ddupan.top homelab 的内部基础设施控制平面,不以通用发行版为初期目标。
|
||||||
- 提交、文档和代码注释优先使用中文;公共 API 标识符和代码遵循对应语言惯例。
|
- 提交、文档和代码注释优先使用中文;公共 API 标识符和代码遵循对应语言惯例。
|
||||||
- 不要重新实现已有成熟后端的核心能力;新增实现前先确认能否通过稳定 API 进行薄适配。
|
- 不要重新实现已有成熟后端的核心能力;新增实现前先确认能否通过稳定 API 进行薄适配。
|
||||||
|
- 在自行设计通用控制循环、资源生命周期、调度、回收或故障恢复机制前,先调查 Kubernetes
|
||||||
|
核心及成熟开源 controller/operator 的实现;优先复用经过验证的模式,并记录有意偏离的
|
||||||
|
理由。
|
||||||
- 不要引入统一包装所有能力的 Application CRD;应用应直接组合正交的平台资源。
|
- 不要引入统一包装所有能力的 Application CRD;应用应直接组合正交的平台资源。
|
||||||
- 所有 controller 必须考虑幂等、observe、finalizer、conditions、删除策略和恢复行为。
|
- 所有 controller 必须考虑幂等、observe、finalizer、conditions、删除策略和恢复行为。
|
||||||
- Secret、token、kubeconfig 及具体生产凭据不得提交到仓库。
|
- Secret、token、kubeconfig 及具体生产凭据不得提交到仓库。
|
||||||
|
|||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
# Build the manager binary
|
||||||
|
# Override BASE_IMAGE to build from another registry, e.g. docker.io/library/golang:1.27.1
|
||||||
|
ARG BASE_IMAGE=golang:1.27.1
|
||||||
|
FROM ${BASE_IMAGE} AS builder
|
||||||
|
ARG TARGETOS
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
WORKDIR /workspace
|
||||||
|
# Copy the Go Modules manifests
|
||||||
|
COPY go.mod go.mod
|
||||||
|
COPY go.sum go.sum
|
||||||
|
# cache deps before building and copying source so that we don't need to re-download as much
|
||||||
|
# and so that source changes don't invalidate our downloaded layer
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# Copy the Go source (relies on .dockerignore to filter)
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build
|
||||||
|
# the GOARCH has no default value to allow the binary to be built according to the host where the command
|
||||||
|
# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
|
||||||
|
# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,
|
||||||
|
# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
|
||||||
|
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go
|
||||||
|
|
||||||
|
# Use distroless as minimal base image to package the manager binary
|
||||||
|
# Refer to https://github.com/GoogleContainerTools/distroless for more details
|
||||||
|
FROM gcr.io/distroless/static:nonroot
|
||||||
|
WORKDIR /
|
||||||
|
COPY --from=builder /workspace/manager .
|
||||||
|
USER 65532:65532
|
||||||
|
|
||||||
|
ENTRYPOINT ["/manager"]
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
# Image URL to use all building/pushing image targets
|
||||||
|
IMG ?= controller:latest
|
||||||
|
# YEAR defines the year value used for substituting the YEAR placeholder in the boilerplate header.
|
||||||
|
YEAR ?= $(shell date +%Y)
|
||||||
|
|
||||||
|
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
|
||||||
|
ifeq (,$(shell go env GOBIN))
|
||||||
|
GOBIN=$(shell go env GOPATH)/bin
|
||||||
|
else
|
||||||
|
GOBIN=$(shell go env GOBIN)
|
||||||
|
endif
|
||||||
|
|
||||||
|
# CONTAINER_TOOL defines the container tool to be used for building images.
|
||||||
|
# Be aware that the target commands are only tested with Docker which is
|
||||||
|
# scaffolded by default. However, you might want to replace it to use other
|
||||||
|
# tools. (i.e. podman)
|
||||||
|
CONTAINER_TOOL ?= docker
|
||||||
|
|
||||||
|
# Setting SHELL to bash allows bash commands to be executed by recipes.
|
||||||
|
# Options are set to exit when a recipe line exits non-zero or a piped command fails.
|
||||||
|
SHELL = /usr/bin/env bash -o pipefail
|
||||||
|
.SHELLFLAGS = -ec
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: build
|
||||||
|
|
||||||
|
##@ General
|
||||||
|
|
||||||
|
# The help target prints out all targets with their descriptions organized
|
||||||
|
# beneath their categories. The categories are represented by '##@' and the
|
||||||
|
# target descriptions by '##'. The awk command is responsible for reading the
|
||||||
|
# entire set of makefiles included in this invocation, looking for lines of the
|
||||||
|
# file as xyz: ## something, and then pretty-format the target and help. Then,
|
||||||
|
# if there's a line with ##@ something, that gets pretty-printed as a category.
|
||||||
|
# More info on the usage of ANSI control characters for terminal formatting:
|
||||||
|
# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
|
||||||
|
# More info on the awk command:
|
||||||
|
# http://linuxcommand.org/lc3_adv_awk.php
|
||||||
|
|
||||||
|
.PHONY: help
|
||||||
|
help: ## Display this help.
|
||||||
|
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
||||||
|
|
||||||
|
##@ Development
|
||||||
|
|
||||||
|
.PHONY: manifests
|
||||||
|
manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
|
||||||
|
"$(CONTROLLER_GEN)" rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
|
||||||
|
|
||||||
|
.PHONY: generate
|
||||||
|
generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
|
||||||
|
"$(CONTROLLER_GEN)" object paths="./..."
|
||||||
|
|
||||||
|
.PHONY: fmt
|
||||||
|
fmt: ## Run go fmt against code.
|
||||||
|
go fmt ./...
|
||||||
|
|
||||||
|
.PHONY: vet
|
||||||
|
vet: ## Run go vet against code.
|
||||||
|
go vet ./...
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test: manifests generate fmt vet setup-envtest ## Run tests.
|
||||||
|
KUBEBUILDER_ASSETS="$(shell "$(ENVTEST)" use $(ENVTEST_K8S_VERSION) --bin-dir "$(LOCALBIN)" -p path)" go test ./... -coverprofile cover.out
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint: golangci-lint ## Run golangci-lint linter
|
||||||
|
"$(GOLANGCI_LINT)" run
|
||||||
|
|
||||||
|
.PHONY: lint-fix
|
||||||
|
lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
|
||||||
|
"$(GOLANGCI_LINT)" run --fix
|
||||||
|
|
||||||
|
.PHONY: lint-config
|
||||||
|
lint-config: golangci-lint ## Verify golangci-lint linter configuration
|
||||||
|
"$(GOLANGCI_LINT)" config verify
|
||||||
|
|
||||||
|
##@ Build
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build: manifests generate fmt vet ## Build manager binary.
|
||||||
|
go build -o bin/manager cmd/main.go
|
||||||
|
|
||||||
|
.PHONY: run
|
||||||
|
run: manifests generate fmt vet ## Run a controller from your host.
|
||||||
|
go run ./cmd/main.go
|
||||||
|
|
||||||
|
# If you wish to build the manager image targeting other platforms you can use the --platform flag.
|
||||||
|
# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.
|
||||||
|
# More info: https://docs.docker.com/develop/develop-images/build_enhancements/
|
||||||
|
# Override BASE_IMAGE to build from another registry, e.g.
|
||||||
|
# make docker-build IMG=<img> BASE_IMAGE=docker.io/library/golang:1.27.1
|
||||||
|
.PHONY: docker-build
|
||||||
|
docker-build: ## Build docker image with the manager.
|
||||||
|
$(CONTAINER_TOOL) build $(if $(BASE_IMAGE),--build-arg BASE_IMAGE=$(BASE_IMAGE)) -t ${IMG} .
|
||||||
|
|
||||||
|
.PHONY: docker-push
|
||||||
|
docker-push: ## Push docker image with the manager.
|
||||||
|
$(CONTAINER_TOOL) push ${IMG}
|
||||||
|
|
||||||
|
# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple
|
||||||
|
# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:
|
||||||
|
# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/
|
||||||
|
# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/
|
||||||
|
# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=<myregistry/image:<tag>> then the export will fail)
|
||||||
|
# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option.
|
||||||
|
PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le
|
||||||
|
.PHONY: docker-buildx
|
||||||
|
docker-buildx: ## Build and push docker image for the manager for cross-platform support
|
||||||
|
# copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile
|
||||||
|
sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross
|
||||||
|
- $(CONTAINER_TOOL) buildx create --name ayatori-builder
|
||||||
|
$(CONTAINER_TOOL) buildx use ayatori-builder
|
||||||
|
- $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) $(if $(BASE_IMAGE),--build-arg BASE_IMAGE=$(BASE_IMAGE)) --tag ${IMG} -f Dockerfile.cross .
|
||||||
|
- $(CONTAINER_TOOL) buildx rm ayatori-builder
|
||||||
|
rm Dockerfile.cross
|
||||||
|
|
||||||
|
.PHONY: build-installer
|
||||||
|
build-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment.
|
||||||
|
mkdir -p dist
|
||||||
|
cd config/manager && "$(KUSTOMIZE)" edit set image controller=${IMG}
|
||||||
|
"$(KUSTOMIZE)" build config/default > dist/install.yaml
|
||||||
|
|
||||||
|
##@ Deployment
|
||||||
|
|
||||||
|
ifndef ignore-not-found
|
||||||
|
ignore-not-found = false
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: install
|
||||||
|
install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
|
||||||
|
@out="$$( "$(KUSTOMIZE)" build config/crd 2>/dev/null || true )"; \
|
||||||
|
if [ -n "$$out" ]; then echo "$$out" | "$(KUBECTL)" apply -f -; else echo "No CRDs to install; skipping."; fi
|
||||||
|
|
||||||
|
.PHONY: uninstall
|
||||||
|
uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
|
||||||
|
@out="$$( "$(KUSTOMIZE)" build config/crd 2>/dev/null || true )"; \
|
||||||
|
if [ -n "$$out" ]; then echo "$$out" | "$(KUBECTL)" delete --ignore-not-found=$(ignore-not-found) -f -; else echo "No CRDs to delete; skipping."; fi
|
||||||
|
|
||||||
|
.PHONY: deploy
|
||||||
|
deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
|
||||||
|
cd config/manager && "$(KUSTOMIZE)" edit set image controller=${IMG}
|
||||||
|
"$(KUSTOMIZE)" build config/default | "$(KUBECTL)" apply -f -
|
||||||
|
|
||||||
|
.PHONY: undeploy
|
||||||
|
undeploy: kustomize ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
|
||||||
|
"$(KUSTOMIZE)" build config/default | "$(KUBECTL)" delete --ignore-not-found=$(ignore-not-found) -f -
|
||||||
|
|
||||||
|
##@ Dependencies
|
||||||
|
|
||||||
|
## Location to install dependencies to
|
||||||
|
LOCALBIN ?= $(shell pwd)/bin
|
||||||
|
$(LOCALBIN):
|
||||||
|
mkdir -p "$(LOCALBIN)"
|
||||||
|
|
||||||
|
## Tool Binaries
|
||||||
|
KUBECTL ?= kubectl
|
||||||
|
KUSTOMIZE ?= $(LOCALBIN)/kustomize
|
||||||
|
CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
|
||||||
|
ENVTEST ?= $(LOCALBIN)/setup-envtest
|
||||||
|
GOLANGCI_LINT = $(LOCALBIN)/golangci-lint
|
||||||
|
|
||||||
|
## Tool Versions
|
||||||
|
KUSTOMIZE_VERSION ?= v5.8.1
|
||||||
|
CONTROLLER_TOOLS_VERSION ?= v0.22.0
|
||||||
|
|
||||||
|
#ENVTEST_VERSION is the controller-runtime version to use for setup-envtest, derived from go.mod
|
||||||
|
ENVTEST_VERSION ?= $(shell v='$(call gomodver,sigs.k8s.io/controller-runtime)'; \
|
||||||
|
[ -n "$$v" ] || { echo "Set ENVTEST_VERSION manually (controller-runtime replace has no tag)" >&2; exit 1; }; \
|
||||||
|
printf '%s\n' "$$v")
|
||||||
|
|
||||||
|
#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31)
|
||||||
|
ENVTEST_K8S_VERSION ?= $(shell v='$(call gomodver,k8s.io/api)'; \
|
||||||
|
[ -n "$$v" ] || { echo "Set ENVTEST_K8S_VERSION manually (k8s.io/api replace has no tag)" >&2; exit 1; }; \
|
||||||
|
printf '%s\n' "$$v" | sed -E 's/^v?[0-9]+\.([0-9]+).*/1.\1/')
|
||||||
|
|
||||||
|
GOLANGCI_LINT_VERSION ?= v2.13.1
|
||||||
|
.PHONY: kustomize
|
||||||
|
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
|
||||||
|
$(KUSTOMIZE): $(LOCALBIN)
|
||||||
|
$(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION))
|
||||||
|
|
||||||
|
.PHONY: controller-gen
|
||||||
|
controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.
|
||||||
|
$(CONTROLLER_GEN): $(LOCALBIN)
|
||||||
|
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION))
|
||||||
|
|
||||||
|
.PHONY: setup-envtest
|
||||||
|
setup-envtest: envtest ## Download the binaries required for ENVTEST in the local bin directory.
|
||||||
|
@echo "Setting up envtest binaries for Kubernetes version $(ENVTEST_K8S_VERSION)..."
|
||||||
|
@"$(ENVTEST)" use $(ENVTEST_K8S_VERSION) --bin-dir "$(LOCALBIN)" -p path || { \
|
||||||
|
echo "Error: Failed to set up envtest binaries for version $(ENVTEST_K8S_VERSION)."; \
|
||||||
|
exit 1; \
|
||||||
|
}
|
||||||
|
|
||||||
|
.PHONY: envtest
|
||||||
|
envtest: $(ENVTEST) ## Download setup-envtest locally if necessary.
|
||||||
|
$(ENVTEST): $(LOCALBIN)
|
||||||
|
$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION))
|
||||||
|
|
||||||
|
.PHONY: golangci-lint
|
||||||
|
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
|
||||||
|
$(GOLANGCI_LINT): $(LOCALBIN)
|
||||||
|
$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION))
|
||||||
|
@test -f .custom-gcl.yml && { \
|
||||||
|
echo "Building custom golangci-lint with plugins..." && \
|
||||||
|
$(GOLANGCI_LINT) custom --destination $(LOCALBIN) --name golangci-lint-custom && \
|
||||||
|
mv -f $(LOCALBIN)/golangci-lint-custom $(GOLANGCI_LINT); \
|
||||||
|
} || true
|
||||||
|
|
||||||
|
# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
|
||||||
|
# $1 - target path with name of binary
|
||||||
|
# $2 - package url which can be installed
|
||||||
|
# $3 - specific version of package
|
||||||
|
define go-install-tool
|
||||||
|
@[ -f "$(1)-$(3)" ] && [ "$$(readlink -- "$(1)" 2>/dev/null)" = "$(1)-$(3)" ] || { \
|
||||||
|
set -e; \
|
||||||
|
package=$(2)@$(3) ;\
|
||||||
|
echo "Downloading $${package}" ;\
|
||||||
|
rm -f "$(1)" ;\
|
||||||
|
GOBIN="$(LOCALBIN)" go install $${package} ;\
|
||||||
|
mv "$(LOCALBIN)/$$(basename "$(1)")" "$(1)-$(3)" ;\
|
||||||
|
} ;\
|
||||||
|
ln -sf "$$(realpath "$(1)-$(3)")" "$(1)"
|
||||||
|
endef
|
||||||
|
|
||||||
|
define gomodver
|
||||||
|
$(shell go list -m -f '{{if .Replace}}{{.Replace.Version}}{{else}}{{.Version}}{{end}}' $(1) 2>/dev/null)
|
||||||
|
endef
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# Code generated by tool. DO NOT EDIT.
|
||||||
|
# This file is used to track the info used to scaffold your project
|
||||||
|
# and allow the plugins properly work.
|
||||||
|
# More info: https://book.kubebuilder.io/reference/project-config.html
|
||||||
|
cliVersion: 4.16.0
|
||||||
|
domain: ayatori.ddupan.top
|
||||||
|
layout:
|
||||||
|
- go.kubebuilder.io/v4
|
||||||
|
multigroup: true
|
||||||
|
projectName: ayatori
|
||||||
|
repo: git.ddupan.top/panxiao81/ayatori
|
||||||
|
resources:
|
||||||
|
- api:
|
||||||
|
crdVersion: v1
|
||||||
|
namespaced: true
|
||||||
|
domain: ayatori.ddupan.top
|
||||||
|
group: execution
|
||||||
|
kind: Job
|
||||||
|
path: git.ddupan.top/panxiao81/ayatori/api/execution/v1alpha1
|
||||||
|
version: v1alpha1
|
||||||
|
- api:
|
||||||
|
crdVersion: v1
|
||||||
|
domain: ayatori.ddupan.top
|
||||||
|
group: execution
|
||||||
|
kind: JobClass
|
||||||
|
path: git.ddupan.top/panxiao81/ayatori/api/execution/v1alpha1
|
||||||
|
version: v1alpha1
|
||||||
|
- api:
|
||||||
|
crdVersion: v1
|
||||||
|
domain: ayatori.ddupan.top
|
||||||
|
group: execution
|
||||||
|
kind: KubernetesExecutionParameters
|
||||||
|
path: git.ddupan.top/panxiao81/ayatori/api/execution/v1alpha1
|
||||||
|
version: v1alpha1
|
||||||
|
- api:
|
||||||
|
crdVersion: v1
|
||||||
|
domain: ayatori.ddupan.top
|
||||||
|
group: execution
|
||||||
|
kind: OpenSandboxExecutionParameters
|
||||||
|
path: git.ddupan.top/panxiao81/ayatori/api/execution/v1alpha1
|
||||||
|
version: v1alpha1
|
||||||
|
version: "3"
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Package v1alpha1 contains API Schema definitions for the execution v1alpha1 API group.
|
||||||
|
// +kubebuilder:object:generate=true
|
||||||
|
// +groupName=execution.ayatori.ddupan.top
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// SchemeGroupVersion is group version used to register these objects.
|
||||||
|
// This name is used by applyconfiguration generators (e.g. controller-gen).
|
||||||
|
SchemeGroupVersion = schema.GroupVersion{Group: "execution.ayatori.ddupan.top", Version: "v1alpha1"}
|
||||||
|
|
||||||
|
// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.
|
||||||
|
GroupVersion = SchemeGroupVersion
|
||||||
|
|
||||||
|
// SchemeBuilder is used to add go types to the GroupVersionKind scheme.
|
||||||
|
SchemeBuilder = runtime.NewSchemeBuilder(func(scheme *runtime.Scheme) error {
|
||||||
|
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// AddToScheme adds the types in this group-version to the given scheme.
|
||||||
|
AddToScheme = SchemeBuilder.AddToScheme
|
||||||
|
)
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
JobConditionAccepted = "Accepted"
|
||||||
|
JobConditionScheduled = "Scheduled"
|
||||||
|
JobConditionSucceeded = "Succeeded"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JobDesiredState string
|
||||||
|
|
||||||
|
const (
|
||||||
|
JobDesiredStateRunning JobDesiredState = "Running"
|
||||||
|
JobDesiredStateCancelled JobDesiredState = "Cancelled"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TaskSpec struct {
|
||||||
|
// +kubebuilder:validation:MinLength=1
|
||||||
|
Image string `json:"image"`
|
||||||
|
// +optional
|
||||||
|
ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"`
|
||||||
|
// +optional
|
||||||
|
Command []string `json:"command,omitempty"`
|
||||||
|
// +optional
|
||||||
|
Args []string `json:"args,omitempty"`
|
||||||
|
// +optional
|
||||||
|
WorkingDir string `json:"workingDir,omitempty"`
|
||||||
|
// +listType=map
|
||||||
|
// +listMapKey=name
|
||||||
|
// +optional
|
||||||
|
Env []EnvVar `json:"env,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:validation:XValidation:rule="has(self.value) != has(self.valueFrom)",message="exactly one of value or valueFrom must be set"
|
||||||
|
type EnvVar struct {
|
||||||
|
// +kubebuilder:validation:Pattern=`^[A-Za-z_][A-Za-z0-9_]*$`
|
||||||
|
Name string `json:"name"`
|
||||||
|
// +optional
|
||||||
|
Value *string `json:"value,omitempty"`
|
||||||
|
// +optional
|
||||||
|
ValueFrom *EnvVarSource `json:"valueFrom,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:validation:XValidation:rule="has(self.secretKeyRef) != has(self.configMapKeyRef)",message="exactly one key reference must be set"
|
||||||
|
type EnvVarSource struct {
|
||||||
|
// +optional
|
||||||
|
SecretKeyRef *corev1.SecretKeySelector `json:"secretKeyRef,omitempty"`
|
||||||
|
// +optional
|
||||||
|
ConfigMapKeyRef *corev1.ConfigMapKeySelector `json:"configMapKeyRef,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResourceValues struct {
|
||||||
|
// +optional
|
||||||
|
CPU *resource.Quantity `json:"cpu,omitempty"`
|
||||||
|
// +optional
|
||||||
|
Memory *resource.Quantity `json:"memory,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExecutionResourceRequirements struct {
|
||||||
|
// +optional
|
||||||
|
Requests ResourceValues `json:"requests,omitempty"`
|
||||||
|
// +optional
|
||||||
|
Limits ResourceValues `json:"limits,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:validation:XValidation:rule="self.task == oldSelf.task",message="task is immutable"
|
||||||
|
// +kubebuilder:validation:XValidation:rule="self.resources == oldSelf.resources",message="resources are immutable"
|
||||||
|
// +kubebuilder:validation:XValidation:rule="has(self.activeDeadlineSeconds) == has(oldSelf.activeDeadlineSeconds) && (!has(self.activeDeadlineSeconds) || self.activeDeadlineSeconds == oldSelf.activeDeadlineSeconds)",message="activeDeadlineSeconds is immutable"
|
||||||
|
// +kubebuilder:validation:XValidation:rule="has(self.jobClassName) == has(oldSelf.jobClassName) && (!has(self.jobClassName) || self.jobClassName == oldSelf.jobClassName)",message="jobClassName is immutable"
|
||||||
|
// +kubebuilder:validation:XValidation:rule="oldSelf.desiredState == self.desiredState || (oldSelf.desiredState == 'Running' && self.desiredState == 'Cancelled')",message="desiredState may only transition from Running to Cancelled"
|
||||||
|
type JobSpec struct {
|
||||||
|
// +optional
|
||||||
|
JobClassName string `json:"jobClassName,omitempty"`
|
||||||
|
Task TaskSpec `json:"task"`
|
||||||
|
// +optional
|
||||||
|
Resources ExecutionResourceRequirements `json:"resources,omitempty"`
|
||||||
|
// +kubebuilder:validation:Minimum=1
|
||||||
|
// +optional
|
||||||
|
ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty"`
|
||||||
|
// +kubebuilder:validation:Minimum=0
|
||||||
|
// +optional
|
||||||
|
TTLSecondsAfterFinished *int32 `json:"ttlSecondsAfterFinished,omitempty"`
|
||||||
|
// +kubebuilder:validation:Enum=Running;Cancelled
|
||||||
|
// +kubebuilder:default=Running
|
||||||
|
// +optional
|
||||||
|
DesiredState JobDesiredState `json:"desiredState,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParametersReference struct {
|
||||||
|
Group string `json:"group"`
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
// +optional
|
||||||
|
UID types.UID `json:"uid,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResolvedJobClassReference struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
UID types.UID `json:"uid"`
|
||||||
|
ControllerName string `json:"controllerName"`
|
||||||
|
ParametersRef ParametersReference `json:"parametersRef"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExecutionReference struct {
|
||||||
|
// +kubebuilder:validation:MinLength=1
|
||||||
|
Type string `json:"type"`
|
||||||
|
// +kubebuilder:validation:MinLength=1
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExecutionStatus struct {
|
||||||
|
Adapter string `json:"adapter"`
|
||||||
|
// +listType=map
|
||||||
|
// +listMapKey=type
|
||||||
|
// +optional
|
||||||
|
References []ExecutionReference `json:"references,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type JobResult struct {
|
||||||
|
// +optional
|
||||||
|
ExitCode *int32 `json:"exitCode,omitempty"`
|
||||||
|
// +optional
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type JobStatus struct {
|
||||||
|
// +optional
|
||||||
|
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||||
|
// +listType=map
|
||||||
|
// +listMapKey=type
|
||||||
|
// +optional
|
||||||
|
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||||
|
// +optional
|
||||||
|
ResolvedJobClass *ResolvedJobClassReference `json:"resolvedJobClass,omitempty"`
|
||||||
|
// +optional
|
||||||
|
EffectiveResources ExecutionResourceRequirements `json:"effectiveResources,omitempty"`
|
||||||
|
// +optional
|
||||||
|
Execution *ExecutionStatus `json:"execution,omitempty"`
|
||||||
|
// +optional
|
||||||
|
StartTime *metav1.Time `json:"startTime,omitempty"`
|
||||||
|
// +optional
|
||||||
|
CompletionTime *metav1.Time `json:"completionTime,omitempty"`
|
||||||
|
// +optional
|
||||||
|
Result *JobResult `json:"result,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:object:root=true
|
||||||
|
// +kubebuilder:subresource:status
|
||||||
|
// +kubebuilder:printcolumn:name="Accepted",type=string,JSONPath=`.status.conditions[?(@.type=='Accepted')].status`
|
||||||
|
// +kubebuilder:printcolumn:name="Scheduled",type=string,JSONPath=`.status.conditions[?(@.type=='Scheduled')].status`
|
||||||
|
// +kubebuilder:printcolumn:name="Succeeded",type=string,JSONPath=`.status.conditions[?(@.type=='Succeeded')].status`
|
||||||
|
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
|
||||||
|
type Job struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitzero"`
|
||||||
|
Spec JobSpec `json:"spec"`
|
||||||
|
// +optional
|
||||||
|
Status JobStatus `json:"status,omitzero"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:object:root=true
|
||||||
|
type JobList struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ListMeta `json:"metadata,omitzero"`
|
||||||
|
Items []Job `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
SchemeBuilder.Register(func(s *runtime.Scheme) error {
|
||||||
|
s.AddKnownTypes(SchemeGroupVersion, &Job{}, &JobList{})
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
JobClassConditionAccepted = "Accepted"
|
||||||
|
JobClassConditionReady = "Ready"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExecutionResourcePolicy struct {
|
||||||
|
// +optional
|
||||||
|
Defaults ExecutionResourceRequirements `json:"defaults,omitempty"`
|
||||||
|
// +optional
|
||||||
|
Minimum ExecutionResourceRequirements `json:"minimum,omitempty"`
|
||||||
|
// +optional
|
||||||
|
Maximum ExecutionResourceRequirements `json:"maximum,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:validation:XValidation:rule="self.controllerName == oldSelf.controllerName",message="controllerName is immutable"
|
||||||
|
// +kubebuilder:validation:XValidation:rule="self.parametersRef == oldSelf.parametersRef",message="parametersRef is immutable"
|
||||||
|
type JobClassSpec struct {
|
||||||
|
// +kubebuilder:validation:MinLength=1
|
||||||
|
// +kubebuilder:validation:MaxLength=253
|
||||||
|
ControllerName string `json:"controllerName"`
|
||||||
|
ParametersRef ParametersReference `json:"parametersRef"`
|
||||||
|
// +optional
|
||||||
|
AllowedNamespaces *metav1.LabelSelector `json:"allowedNamespaces,omitempty"`
|
||||||
|
// +optional
|
||||||
|
Resources ExecutionResourcePolicy `json:"resources,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type JobClassStatus struct {
|
||||||
|
// +optional
|
||||||
|
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||||
|
// +listType=map
|
||||||
|
// +listMapKey=type
|
||||||
|
// +optional
|
||||||
|
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:object:root=true
|
||||||
|
// +kubebuilder:subresource:status
|
||||||
|
// +kubebuilder:resource:scope=Cluster
|
||||||
|
// +kubebuilder:printcolumn:name="Controller",type=string,JSONPath=`.spec.controllerName`
|
||||||
|
// +kubebuilder:printcolumn:name="Accepted",type=string,JSONPath=`.status.conditions[?(@.type=='Accepted')].status`
|
||||||
|
// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=='Ready')].status`
|
||||||
|
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
|
||||||
|
type JobClass struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitzero"`
|
||||||
|
Spec JobClassSpec `json:"spec"`
|
||||||
|
// +optional
|
||||||
|
Status JobClassStatus `json:"status,omitzero"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:object:root=true
|
||||||
|
type JobClassList struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ListMeta `json:"metadata,omitzero"`
|
||||||
|
Items []JobClass `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
SchemeBuilder.Register(func(s *runtime.Scheme) error {
|
||||||
|
s.AddKnownTypes(SchemeGroupVersion, &JobClass{}, &JobClassList{})
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KubernetesSchedulingParameters struct {
|
||||||
|
// +optional
|
||||||
|
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
|
||||||
|
// +optional
|
||||||
|
Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KubernetesExecutionParametersSpec struct {
|
||||||
|
// +kubebuilder:validation:MinLength=1
|
||||||
|
ServiceAccountName string `json:"serviceAccountName"`
|
||||||
|
// +optional
|
||||||
|
RuntimeClassName string `json:"runtimeClassName,omitempty"`
|
||||||
|
// +optional
|
||||||
|
Scheduling KubernetesSchedulingParameters `json:"scheduling,omitempty"`
|
||||||
|
// +optional
|
||||||
|
PodSecurityContext *corev1.PodSecurityContext `json:"podSecurityContext,omitempty"`
|
||||||
|
// +kubebuilder:validation:Enum=Always;Never;IfNotPresent
|
||||||
|
// +kubebuilder:default=IfNotPresent
|
||||||
|
// +optional
|
||||||
|
ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:object:root=true
|
||||||
|
// +kubebuilder:resource:scope=Cluster
|
||||||
|
type KubernetesExecutionParameters struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitzero"`
|
||||||
|
Spec KubernetesExecutionParametersSpec `json:"spec"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:object:root=true
|
||||||
|
type KubernetesExecutionParametersList struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ListMeta `json:"metadata,omitzero"`
|
||||||
|
Items []KubernetesExecutionParameters `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
SchemeBuilder.Register(func(s *runtime.Scheme) error {
|
||||||
|
s.AddKnownTypes(SchemeGroupVersion, &KubernetesExecutionParameters{}, &KubernetesExecutionParametersList{})
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NamespacedKeyReference struct {
|
||||||
|
Namespace string `json:"namespace"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OpenSandboxRequestMapping string
|
||||||
|
|
||||||
|
const (
|
||||||
|
OpenSandboxRequestMappingAdmissionOnly OpenSandboxRequestMapping = "AdmissionOnly"
|
||||||
|
OpenSandboxRequestMappingNative OpenSandboxRequestMapping = "Native"
|
||||||
|
)
|
||||||
|
|
||||||
|
// +kubebuilder:validation:XValidation:rule="self.allowInsecureHTTP || self.endpoint.startsWith('https://')",message="endpoint must use HTTPS unless allowInsecureHTTP is true"
|
||||||
|
type OpenSandboxExecutionParametersSpec struct {
|
||||||
|
// +kubebuilder:validation:MinLength=1
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
APIKeySecretRef NamespacedKeyReference `json:"apiKeySecretRef"`
|
||||||
|
// +optional
|
||||||
|
PoolRef string `json:"poolRef,omitempty"`
|
||||||
|
// +kubebuilder:validation:Enum=AdmissionOnly;Native
|
||||||
|
// +kubebuilder:default=AdmissionOnly
|
||||||
|
// +optional
|
||||||
|
RequestMapping OpenSandboxRequestMapping `json:"requestMapping,omitempty"`
|
||||||
|
// +optional
|
||||||
|
AllowSecretEnv bool `json:"allowSecretEnv,omitempty"`
|
||||||
|
// +optional
|
||||||
|
AllowImageAuth bool `json:"allowImageAuth,omitempty"`
|
||||||
|
// AllowInsecureHTTP is intended for isolated development environments only.
|
||||||
|
// +optional
|
||||||
|
AllowInsecureHTTP bool `json:"allowInsecureHTTP,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:object:root=true
|
||||||
|
// +kubebuilder:resource:scope=Cluster
|
||||||
|
type OpenSandboxExecutionParameters struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitzero"`
|
||||||
|
Spec OpenSandboxExecutionParametersSpec `json:"spec"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:object:root=true
|
||||||
|
type OpenSandboxExecutionParametersList struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ListMeta `json:"metadata,omitzero"`
|
||||||
|
Items []OpenSandboxExecutionParameters `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
SchemeBuilder.Register(func(s *runtime.Scheme) error {
|
||||||
|
s.AddKnownTypes(SchemeGroupVersion, &OpenSandboxExecutionParameters{}, &OpenSandboxExecutionParametersList{})
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package v1alpha1_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
executionv1alpha1 "git.ddupan.top/panxiao81/ayatori/api/execution/v1alpha1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestJobCRDValidation(t *testing.T) {
|
||||||
|
if os.Getenv("KUBEBUILDER_ASSETS") == "" {
|
||||||
|
t.Skip("KUBEBUILDER_ASSETS is unset; run make test to execute API integration tests")
|
||||||
|
}
|
||||||
|
|
||||||
|
scheme := runtime.NewScheme()
|
||||||
|
if err := executionv1alpha1.AddToScheme(scheme); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := corev1.AddToScheme(scheme); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
crdPath, err := filepath.Abs("../../../config/crd/bases")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
environment := &envtest.Environment{CRDDirectoryPaths: []string{crdPath}}
|
||||||
|
config, err := environment.Start()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("start envtest: %v", err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := environment.Stop(); err != nil {
|
||||||
|
t.Errorf("stop envtest: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
client, err := ctrlclient.New(config, ctrlclient.Options{Scheme: scheme})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
//nolint:modernize // ObjectMeta is promoted through embedded TypeMeta; embedlit produces invalid Go here.
|
||||||
|
namespace := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "api-validation"}}
|
||||||
|
if err := client.Create(ctx, namespace); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("defaults desired state", func(t *testing.T) {
|
||||||
|
job := validJob("defaults")
|
||||||
|
if err := client.Create(ctx, job); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if job.Spec.DesiredState != executionv1alpha1.JobDesiredStateRunning {
|
||||||
|
t.Fatalf("desiredState = %q, want Running", job.Spec.DesiredState)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("rejects ambiguous environment value", func(t *testing.T) {
|
||||||
|
literal := "visible"
|
||||||
|
job := validJob("invalid-env")
|
||||||
|
job.Spec.Task.Env = []executionv1alpha1.EnvVar{{
|
||||||
|
Name: "TOKEN",
|
||||||
|
Value: &literal,
|
||||||
|
ValueFrom: &executionv1alpha1.EnvVarSource{
|
||||||
|
//nolint:modernize // LocalObjectReference is an embedded Kubernetes API field.
|
||||||
|
SecretKeyRef: &corev1.SecretKeySelector{LocalObjectReference: corev1.LocalObjectReference{Name: "token"}, Key: "value"},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
if err := client.Create(ctx, job); !apierrors.IsInvalid(err) {
|
||||||
|
t.Fatalf("Create() error = %v, want Invalid", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("rejects immutable task update", func(t *testing.T) {
|
||||||
|
job := validJob("immutable")
|
||||||
|
if err := client.Create(ctx, job); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
job.Spec.Task.Image = "docker.io/library/busybox:1.37"
|
||||||
|
if err := client.Update(ctx, job); !apierrors.IsInvalid(err) {
|
||||||
|
t.Fatalf("Update() error = %v, want Invalid", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("allows one-way cancellation", func(t *testing.T) {
|
||||||
|
job := validJob("cancel")
|
||||||
|
if err := client.Create(ctx, job); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
job.Spec.DesiredState = executionv1alpha1.JobDesiredStateCancelled
|
||||||
|
if err := client.Update(ctx, job); err != nil {
|
||||||
|
t.Fatalf("cancel update: %v", err)
|
||||||
|
}
|
||||||
|
job.Spec.DesiredState = executionv1alpha1.JobDesiredStateRunning
|
||||||
|
if err := client.Update(ctx, job); !apierrors.IsInvalid(err) {
|
||||||
|
t.Fatalf("reverse cancellation error = %v, want Invalid", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func validJob(name string) *executionv1alpha1.Job {
|
||||||
|
//nolint:modernize // ObjectMeta is promoted through embedded TypeMeta; embedlit produces invalid Go here.
|
||||||
|
return &executionv1alpha1.Job{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "api-validation"},
|
||||||
|
Spec: executionv1alpha1.JobSpec{
|
||||||
|
Task: executionv1alpha1.TaskSpec{Image: "docker.io/library/alpine:3.22"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,676 @@
|
|||||||
|
//go:build !ignore_autogenerated
|
||||||
|
|
||||||
|
// Code generated by controller-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *EnvVar) DeepCopyInto(out *EnvVar) {
|
||||||
|
*out = *in
|
||||||
|
if in.Value != nil {
|
||||||
|
in, out := &in.Value, &out.Value
|
||||||
|
*out = new(string)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.ValueFrom != nil {
|
||||||
|
in, out := &in.ValueFrom, &out.ValueFrom
|
||||||
|
*out = new(EnvVarSource)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvVar.
|
||||||
|
func (in *EnvVar) DeepCopy() *EnvVar {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(EnvVar)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *EnvVarSource) DeepCopyInto(out *EnvVarSource) {
|
||||||
|
*out = *in
|
||||||
|
if in.SecretKeyRef != nil {
|
||||||
|
in, out := &in.SecretKeyRef, &out.SecretKeyRef
|
||||||
|
*out = new(v1.SecretKeySelector)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.ConfigMapKeyRef != nil {
|
||||||
|
in, out := &in.ConfigMapKeyRef, &out.ConfigMapKeyRef
|
||||||
|
*out = new(v1.ConfigMapKeySelector)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvVarSource.
|
||||||
|
func (in *EnvVarSource) DeepCopy() *EnvVarSource {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(EnvVarSource)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *ExecutionReference) DeepCopyInto(out *ExecutionReference) {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExecutionReference.
|
||||||
|
func (in *ExecutionReference) DeepCopy() *ExecutionReference {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(ExecutionReference)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *ExecutionResourcePolicy) DeepCopyInto(out *ExecutionResourcePolicy) {
|
||||||
|
*out = *in
|
||||||
|
in.Defaults.DeepCopyInto(&out.Defaults)
|
||||||
|
in.Minimum.DeepCopyInto(&out.Minimum)
|
||||||
|
in.Maximum.DeepCopyInto(&out.Maximum)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExecutionResourcePolicy.
|
||||||
|
func (in *ExecutionResourcePolicy) DeepCopy() *ExecutionResourcePolicy {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(ExecutionResourcePolicy)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *ExecutionResourceRequirements) DeepCopyInto(out *ExecutionResourceRequirements) {
|
||||||
|
*out = *in
|
||||||
|
in.Requests.DeepCopyInto(&out.Requests)
|
||||||
|
in.Limits.DeepCopyInto(&out.Limits)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExecutionResourceRequirements.
|
||||||
|
func (in *ExecutionResourceRequirements) DeepCopy() *ExecutionResourceRequirements {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(ExecutionResourceRequirements)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *ExecutionStatus) DeepCopyInto(out *ExecutionStatus) {
|
||||||
|
*out = *in
|
||||||
|
if in.References != nil {
|
||||||
|
in, out := &in.References, &out.References
|
||||||
|
*out = make([]ExecutionReference, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExecutionStatus.
|
||||||
|
func (in *ExecutionStatus) DeepCopy() *ExecutionStatus {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(ExecutionStatus)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Job) DeepCopyInto(out *Job) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||||
|
in.Spec.DeepCopyInto(&out.Spec)
|
||||||
|
in.Status.DeepCopyInto(&out.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Job.
|
||||||
|
func (in *Job) DeepCopy() *Job {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Job)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *Job) 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 *JobClass) DeepCopyInto(out *JobClass) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||||
|
in.Spec.DeepCopyInto(&out.Spec)
|
||||||
|
in.Status.DeepCopyInto(&out.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JobClass.
|
||||||
|
func (in *JobClass) DeepCopy() *JobClass {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(JobClass)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *JobClass) 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 *JobClassList) DeepCopyInto(out *JobClassList) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||||
|
if in.Items != nil {
|
||||||
|
in, out := &in.Items, &out.Items
|
||||||
|
*out = make([]JobClass, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JobClassList.
|
||||||
|
func (in *JobClassList) DeepCopy() *JobClassList {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(JobClassList)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *JobClassList) 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 *JobClassSpec) DeepCopyInto(out *JobClassSpec) {
|
||||||
|
*out = *in
|
||||||
|
out.ParametersRef = in.ParametersRef
|
||||||
|
if in.AllowedNamespaces != nil {
|
||||||
|
in, out := &in.AllowedNamespaces, &out.AllowedNamespaces
|
||||||
|
*out = new(metav1.LabelSelector)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
in.Resources.DeepCopyInto(&out.Resources)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JobClassSpec.
|
||||||
|
func (in *JobClassSpec) DeepCopy() *JobClassSpec {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(JobClassSpec)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *JobClassStatus) DeepCopyInto(out *JobClassStatus) {
|
||||||
|
*out = *in
|
||||||
|
if in.Conditions != nil {
|
||||||
|
in, out := &in.Conditions, &out.Conditions
|
||||||
|
*out = make([]metav1.Condition, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JobClassStatus.
|
||||||
|
func (in *JobClassStatus) DeepCopy() *JobClassStatus {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(JobClassStatus)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *JobList) DeepCopyInto(out *JobList) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||||
|
if in.Items != nil {
|
||||||
|
in, out := &in.Items, &out.Items
|
||||||
|
*out = make([]Job, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JobList.
|
||||||
|
func (in *JobList) DeepCopy() *JobList {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(JobList)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *JobList) 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 *JobResult) DeepCopyInto(out *JobResult) {
|
||||||
|
*out = *in
|
||||||
|
if in.ExitCode != nil {
|
||||||
|
in, out := &in.ExitCode, &out.ExitCode
|
||||||
|
*out = new(int32)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JobResult.
|
||||||
|
func (in *JobResult) DeepCopy() *JobResult {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(JobResult)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *JobSpec) DeepCopyInto(out *JobSpec) {
|
||||||
|
*out = *in
|
||||||
|
in.Task.DeepCopyInto(&out.Task)
|
||||||
|
in.Resources.DeepCopyInto(&out.Resources)
|
||||||
|
if in.ActiveDeadlineSeconds != nil {
|
||||||
|
in, out := &in.ActiveDeadlineSeconds, &out.ActiveDeadlineSeconds
|
||||||
|
*out = new(int64)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.TTLSecondsAfterFinished != nil {
|
||||||
|
in, out := &in.TTLSecondsAfterFinished, &out.TTLSecondsAfterFinished
|
||||||
|
*out = new(int32)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JobSpec.
|
||||||
|
func (in *JobSpec) DeepCopy() *JobSpec {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(JobSpec)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *JobStatus) DeepCopyInto(out *JobStatus) {
|
||||||
|
*out = *in
|
||||||
|
if in.Conditions != nil {
|
||||||
|
in, out := &in.Conditions, &out.Conditions
|
||||||
|
*out = make([]metav1.Condition, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if in.ResolvedJobClass != nil {
|
||||||
|
in, out := &in.ResolvedJobClass, &out.ResolvedJobClass
|
||||||
|
*out = new(ResolvedJobClassReference)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
in.EffectiveResources.DeepCopyInto(&out.EffectiveResources)
|
||||||
|
if in.Execution != nil {
|
||||||
|
in, out := &in.Execution, &out.Execution
|
||||||
|
*out = new(ExecutionStatus)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.StartTime != nil {
|
||||||
|
in, out := &in.StartTime, &out.StartTime
|
||||||
|
*out = (*in).DeepCopy()
|
||||||
|
}
|
||||||
|
if in.CompletionTime != nil {
|
||||||
|
in, out := &in.CompletionTime, &out.CompletionTime
|
||||||
|
*out = (*in).DeepCopy()
|
||||||
|
}
|
||||||
|
if in.Result != nil {
|
||||||
|
in, out := &in.Result, &out.Result
|
||||||
|
*out = new(JobResult)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JobStatus.
|
||||||
|
func (in *JobStatus) DeepCopy() *JobStatus {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(JobStatus)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *KubernetesExecutionParameters) DeepCopyInto(out *KubernetesExecutionParameters) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||||
|
in.Spec.DeepCopyInto(&out.Spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesExecutionParameters.
|
||||||
|
func (in *KubernetesExecutionParameters) DeepCopy() *KubernetesExecutionParameters {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(KubernetesExecutionParameters)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *KubernetesExecutionParameters) 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 *KubernetesExecutionParametersList) DeepCopyInto(out *KubernetesExecutionParametersList) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||||
|
if in.Items != nil {
|
||||||
|
in, out := &in.Items, &out.Items
|
||||||
|
*out = make([]KubernetesExecutionParameters, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesExecutionParametersList.
|
||||||
|
func (in *KubernetesExecutionParametersList) DeepCopy() *KubernetesExecutionParametersList {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(KubernetesExecutionParametersList)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *KubernetesExecutionParametersList) 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 *KubernetesExecutionParametersSpec) DeepCopyInto(out *KubernetesExecutionParametersSpec) {
|
||||||
|
*out = *in
|
||||||
|
in.Scheduling.DeepCopyInto(&out.Scheduling)
|
||||||
|
if in.PodSecurityContext != nil {
|
||||||
|
in, out := &in.PodSecurityContext, &out.PodSecurityContext
|
||||||
|
*out = new(v1.PodSecurityContext)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesExecutionParametersSpec.
|
||||||
|
func (in *KubernetesExecutionParametersSpec) DeepCopy() *KubernetesExecutionParametersSpec {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(KubernetesExecutionParametersSpec)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *KubernetesSchedulingParameters) DeepCopyInto(out *KubernetesSchedulingParameters) {
|
||||||
|
*out = *in
|
||||||
|
if in.NodeSelector != nil {
|
||||||
|
in, out := &in.NodeSelector, &out.NodeSelector
|
||||||
|
*out = make(map[string]string, len(*in))
|
||||||
|
for key, val := range *in {
|
||||||
|
(*out)[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if in.Tolerations != nil {
|
||||||
|
in, out := &in.Tolerations, &out.Tolerations
|
||||||
|
*out = make([]v1.Toleration, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesSchedulingParameters.
|
||||||
|
func (in *KubernetesSchedulingParameters) DeepCopy() *KubernetesSchedulingParameters {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(KubernetesSchedulingParameters)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *NamespacedKeyReference) DeepCopyInto(out *NamespacedKeyReference) {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedKeyReference.
|
||||||
|
func (in *NamespacedKeyReference) DeepCopy() *NamespacedKeyReference {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(NamespacedKeyReference)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *OpenSandboxExecutionParameters) DeepCopyInto(out *OpenSandboxExecutionParameters) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||||
|
out.Spec = in.Spec
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenSandboxExecutionParameters.
|
||||||
|
func (in *OpenSandboxExecutionParameters) DeepCopy() *OpenSandboxExecutionParameters {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(OpenSandboxExecutionParameters)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *OpenSandboxExecutionParameters) 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 *OpenSandboxExecutionParametersList) DeepCopyInto(out *OpenSandboxExecutionParametersList) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||||
|
if in.Items != nil {
|
||||||
|
in, out := &in.Items, &out.Items
|
||||||
|
*out = make([]OpenSandboxExecutionParameters, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenSandboxExecutionParametersList.
|
||||||
|
func (in *OpenSandboxExecutionParametersList) DeepCopy() *OpenSandboxExecutionParametersList {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(OpenSandboxExecutionParametersList)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *OpenSandboxExecutionParametersList) 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 *OpenSandboxExecutionParametersSpec) DeepCopyInto(out *OpenSandboxExecutionParametersSpec) {
|
||||||
|
*out = *in
|
||||||
|
out.APIKeySecretRef = in.APIKeySecretRef
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenSandboxExecutionParametersSpec.
|
||||||
|
func (in *OpenSandboxExecutionParametersSpec) DeepCopy() *OpenSandboxExecutionParametersSpec {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(OpenSandboxExecutionParametersSpec)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *ParametersReference) DeepCopyInto(out *ParametersReference) {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParametersReference.
|
||||||
|
func (in *ParametersReference) DeepCopy() *ParametersReference {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(ParametersReference)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *ResolvedJobClassReference) DeepCopyInto(out *ResolvedJobClassReference) {
|
||||||
|
*out = *in
|
||||||
|
out.ParametersRef = in.ParametersRef
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResolvedJobClassReference.
|
||||||
|
func (in *ResolvedJobClassReference) DeepCopy() *ResolvedJobClassReference {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(ResolvedJobClassReference)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *ResourceValues) DeepCopyInto(out *ResourceValues) {
|
||||||
|
*out = *in
|
||||||
|
if in.CPU != nil {
|
||||||
|
in, out := &in.CPU, &out.CPU
|
||||||
|
x := (*in).DeepCopy()
|
||||||
|
*out = &x
|
||||||
|
}
|
||||||
|
if in.Memory != nil {
|
||||||
|
in, out := &in.Memory, &out.Memory
|
||||||
|
x := (*in).DeepCopy()
|
||||||
|
*out = &x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceValues.
|
||||||
|
func (in *ResourceValues) DeepCopy() *ResourceValues {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(ResourceValues)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *TaskSpec) DeepCopyInto(out *TaskSpec) {
|
||||||
|
*out = *in
|
||||||
|
if in.ImagePullSecrets != nil {
|
||||||
|
in, out := &in.ImagePullSecrets, &out.ImagePullSecrets
|
||||||
|
*out = make([]v1.LocalObjectReference, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.Command != nil {
|
||||||
|
in, out := &in.Command, &out.Command
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.Args != nil {
|
||||||
|
in, out := &in.Args, &out.Args
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.Env != nil {
|
||||||
|
in, out := &in.Env, &out.Env
|
||||||
|
*out = make([]EnvVar, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskSpec.
|
||||||
|
func (in *TaskSpec) DeepCopy() *TaskSpec {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(TaskSpec)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
+184
@@ -0,0 +1,184 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
|
||||||
|
// to ensure that exec-entrypoint and run can make use of them.
|
||||||
|
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
|
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||||
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/healthz"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
|
||||||
|
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||||
|
|
||||||
|
executionv1alpha1 "git.ddupan.top/panxiao81/ayatori/api/execution/v1alpha1"
|
||||||
|
// +kubebuilder:scaffold:imports
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
scheme = runtime.NewScheme()
|
||||||
|
setupLog = ctrl.Log.WithName("setup")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
|
||||||
|
|
||||||
|
utilruntime.Must(executionv1alpha1.AddToScheme(scheme))
|
||||||
|
// +kubebuilder:scaffold:scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint:gocyclo
|
||||||
|
func main() {
|
||||||
|
var metricsAddr string
|
||||||
|
var metricsCertPath, metricsCertName, metricsCertKey string
|
||||||
|
var webhookCertPath, webhookCertName, webhookCertKey string
|
||||||
|
var webhookPort int
|
||||||
|
var enableLeaderElection bool
|
||||||
|
var probeAddr string
|
||||||
|
var secureMetrics bool
|
||||||
|
var enableHTTP2 bool
|
||||||
|
var tlsOpts []func(*tls.Config)
|
||||||
|
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
|
||||||
|
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
|
||||||
|
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
|
||||||
|
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
|
||||||
|
"Enable leader election for controller manager. "+
|
||||||
|
"Enabling this will ensure there is only one active controller manager.")
|
||||||
|
flag.BoolVar(&secureMetrics, "metrics-secure", true,
|
||||||
|
"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.")
|
||||||
|
flag.StringVar(&webhookCertPath, "webhook-cert-path", "", "The directory that contains the webhook certificate.")
|
||||||
|
flag.StringVar(&webhookCertName, "webhook-cert-name", "tls.crt", "The name of the webhook certificate file.")
|
||||||
|
flag.StringVar(&webhookCertKey, "webhook-cert-key", "tls.key", "The name of the webhook key file.")
|
||||||
|
flag.IntVar(&webhookPort, "webhook-port", 9443, "Port the webhook server listens on. "+
|
||||||
|
"Defaults to 9443. Set -1 to disable the webhook server.")
|
||||||
|
flag.StringVar(&metricsCertPath, "metrics-cert-path", "",
|
||||||
|
"The directory that contains the metrics server certificate.")
|
||||||
|
flag.StringVar(&metricsCertName, "metrics-cert-name", "tls.crt", "The name of the metrics server certificate file.")
|
||||||
|
flag.StringVar(&metricsCertKey, "metrics-cert-key", "tls.key", "The name of the metrics server key file.")
|
||||||
|
flag.BoolVar(&enableHTTP2, "enable-http2", false,
|
||||||
|
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
|
||||||
|
opts := zap.Options{
|
||||||
|
Development: true,
|
||||||
|
}
|
||||||
|
opts.BindFlags(flag.CommandLine)
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
|
||||||
|
|
||||||
|
// if the enable-http2 flag is false (the default), http/2 should be disabled
|
||||||
|
// due to its vulnerabilities. More specifically, disabling http/2 will
|
||||||
|
// prevent from being vulnerable to the HTTP/2 Stream Cancellation and
|
||||||
|
// Rapid Reset CVEs. For more information see:
|
||||||
|
// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3
|
||||||
|
// - https://github.com/advisories/GHSA-4374-p667-p6c8
|
||||||
|
disableHTTP2 := func(c *tls.Config) {
|
||||||
|
setupLog.Info("Disabling HTTP/2")
|
||||||
|
c.NextProtos = []string{"http/1.1"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !enableHTTP2 {
|
||||||
|
tlsOpts = append(tlsOpts, disableHTTP2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial webhook TLS options
|
||||||
|
webhookTLSOpts := tlsOpts
|
||||||
|
webhookServerOptions := webhook.Options{
|
||||||
|
TLSOpts: webhookTLSOpts,
|
||||||
|
Port: webhookPort,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(webhookCertPath) > 0 {
|
||||||
|
setupLog.Info("Initializing webhook certificate watcher using provided certificates",
|
||||||
|
"webhook-cert-path", webhookCertPath, "webhook-cert-name", webhookCertName, "webhook-cert-key", webhookCertKey)
|
||||||
|
|
||||||
|
webhookServerOptions.CertDir = webhookCertPath
|
||||||
|
webhookServerOptions.CertName = webhookCertName
|
||||||
|
webhookServerOptions.KeyName = webhookCertKey
|
||||||
|
}
|
||||||
|
|
||||||
|
webhookServer := webhook.NewServer(webhookServerOptions)
|
||||||
|
|
||||||
|
// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.
|
||||||
|
// More info:
|
||||||
|
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/metrics/server
|
||||||
|
// - https://book.kubebuilder.io/reference/metrics.html
|
||||||
|
metricsServerOptions := metricsserver.Options{
|
||||||
|
BindAddress: metricsAddr,
|
||||||
|
SecureServing: secureMetrics,
|
||||||
|
TLSOpts: tlsOpts,
|
||||||
|
}
|
||||||
|
|
||||||
|
if secureMetrics {
|
||||||
|
// FilterProvider is used to protect the metrics endpoint with authn/authz.
|
||||||
|
// These configurations ensure that only authorized users and service accounts
|
||||||
|
// can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info:
|
||||||
|
// https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/metrics/filters#WithAuthenticationAndAuthorization
|
||||||
|
metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the certificate is not specified, controller-runtime will automatically
|
||||||
|
// generate self-signed certificates for the metrics server. While convenient for development and testing,
|
||||||
|
// this setup is not recommended for production.
|
||||||
|
//
|
||||||
|
// TODO(user): If you enable certManager, uncomment the following lines:
|
||||||
|
// - [METRICS-WITH-CERTS] at config/default/kustomization.yaml to generate and use certificates
|
||||||
|
// managed by cert-manager for the metrics server.
|
||||||
|
// - [PROMETHEUS-WITH-CERTS] at config/prometheus/kustomization.yaml for TLS certification.
|
||||||
|
if len(metricsCertPath) > 0 {
|
||||||
|
setupLog.Info("Initializing metrics certificate watcher using provided certificates",
|
||||||
|
"metrics-cert-path", metricsCertPath, "metrics-cert-name", metricsCertName, "metrics-cert-key", metricsCertKey)
|
||||||
|
|
||||||
|
metricsServerOptions.CertDir = metricsCertPath
|
||||||
|
metricsServerOptions.CertName = metricsCertName
|
||||||
|
metricsServerOptions.KeyName = metricsCertKey
|
||||||
|
}
|
||||||
|
|
||||||
|
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
|
||||||
|
Scheme: scheme,
|
||||||
|
Metrics: metricsServerOptions,
|
||||||
|
WebhookServer: webhookServer,
|
||||||
|
HealthProbeBindAddress: probeAddr,
|
||||||
|
LeaderElection: enableLeaderElection,
|
||||||
|
LeaderElectionID: "a6325ed6.ddupan.top",
|
||||||
|
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
|
||||||
|
// when the Manager ends. This requires the binary to immediately end when the
|
||||||
|
// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
|
||||||
|
// speeds up voluntary leader transitions as the new leader don't have to wait
|
||||||
|
// LeaseDuration time first.
|
||||||
|
//
|
||||||
|
// In the default scaffold provided, the program ends immediately after
|
||||||
|
// the manager stops, so would be fine to enable this option. However,
|
||||||
|
// if you are doing or is intended to do any operation such as perform cleanups
|
||||||
|
// after the manager stops then its usage might be unsafe.
|
||||||
|
// LeaderElectionReleaseOnCancel: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
setupLog.Error(err, "Failed to start manager")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:scaffold:builder
|
||||||
|
|
||||||
|
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
|
||||||
|
setupLog.Error(err, "Failed to set up health check")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
|
||||||
|
setupLog.Error(err, "Failed to set up ready check")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
setupLog.Info("Starting manager")
|
||||||
|
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
|
||||||
|
setupLog.Error(err, "Failed to run manager")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,307 @@
|
|||||||
|
---
|
||||||
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
controller-gen.kubebuilder.io/version: v0.22.0
|
||||||
|
name: jobclasses.execution.ayatori.ddupan.top
|
||||||
|
spec:
|
||||||
|
group: execution.ayatori.ddupan.top
|
||||||
|
names:
|
||||||
|
kind: JobClass
|
||||||
|
listKind: JobClassList
|
||||||
|
plural: jobclasses
|
||||||
|
singular: jobclass
|
||||||
|
scope: Cluster
|
||||||
|
versions:
|
||||||
|
- additionalPrinterColumns:
|
||||||
|
- jsonPath: .spec.controllerName
|
||||||
|
name: Controller
|
||||||
|
type: string
|
||||||
|
- jsonPath: .status.conditions[?(@.type=='Accepted')].status
|
||||||
|
name: Accepted
|
||||||
|
type: string
|
||||||
|
- jsonPath: .status.conditions[?(@.type=='Ready')].status
|
||||||
|
name: Ready
|
||||||
|
type: string
|
||||||
|
- jsonPath: .metadata.creationTimestamp
|
||||||
|
name: Age
|
||||||
|
type: date
|
||||||
|
name: v1alpha1
|
||||||
|
schema:
|
||||||
|
openAPIV3Schema:
|
||||||
|
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:
|
||||||
|
properties:
|
||||||
|
allowedNamespaces:
|
||||||
|
description: |-
|
||||||
|
A label selector is a label query over a set of resources. The result of matchLabels and
|
||||||
|
matchExpressions are ANDed. An empty label selector matches all objects. A null
|
||||||
|
label selector matches no objects.
|
||||||
|
properties:
|
||||||
|
matchExpressions:
|
||||||
|
description: matchExpressions is a list of label selector requirements.
|
||||||
|
The requirements are ANDed.
|
||||||
|
items:
|
||||||
|
description: |-
|
||||||
|
A label selector requirement is a selector that contains values, a key, and an operator that
|
||||||
|
relates the key and values.
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
description: key is the label key that the selector applies
|
||||||
|
to.
|
||||||
|
type: string
|
||||||
|
operator:
|
||||||
|
description: |-
|
||||||
|
operator represents a key's relationship to a set of values.
|
||||||
|
Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||||
|
type: string
|
||||||
|
values:
|
||||||
|
description: |-
|
||||||
|
values is an array of string values. If the operator is In or NotIn,
|
||||||
|
the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||||
|
the values array must be empty. This array is replaced during a strategic
|
||||||
|
merge patch.
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
x-kubernetes-list-type: atomic
|
||||||
|
required:
|
||||||
|
- key
|
||||||
|
- operator
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
x-kubernetes-list-type: atomic
|
||||||
|
matchLabels:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
description: |-
|
||||||
|
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||||
|
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||||
|
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
|
x-kubernetes-map-type: atomic
|
||||||
|
controllerName:
|
||||||
|
maxLength: 253
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
parametersRef:
|
||||||
|
properties:
|
||||||
|
group:
|
||||||
|
type: string
|
||||||
|
kind:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
uid:
|
||||||
|
description: |-
|
||||||
|
UID is a type that holds unique ID values, including UUIDs. Because we
|
||||||
|
don't ONLY use UUIDs, this is an alias to string. Being a type captures
|
||||||
|
intent and helps make sure that UIDs and names do not get conflated.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- group
|
||||||
|
- kind
|
||||||
|
- name
|
||||||
|
type: object
|
||||||
|
resources:
|
||||||
|
properties:
|
||||||
|
defaults:
|
||||||
|
properties:
|
||||||
|
limits:
|
||||||
|
properties:
|
||||||
|
cpu:
|
||||||
|
anyOf:
|
||||||
|
- type: integer
|
||||||
|
- type: string
|
||||||
|
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||||
|
x-kubernetes-int-or-string: true
|
||||||
|
memory:
|
||||||
|
anyOf:
|
||||||
|
- type: integer
|
||||||
|
- type: string
|
||||||
|
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||||
|
x-kubernetes-int-or-string: true
|
||||||
|
type: object
|
||||||
|
requests:
|
||||||
|
properties:
|
||||||
|
cpu:
|
||||||
|
anyOf:
|
||||||
|
- type: integer
|
||||||
|
- type: string
|
||||||
|
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||||
|
x-kubernetes-int-or-string: true
|
||||||
|
memory:
|
||||||
|
anyOf:
|
||||||
|
- type: integer
|
||||||
|
- type: string
|
||||||
|
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||||
|
x-kubernetes-int-or-string: true
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
|
maximum:
|
||||||
|
properties:
|
||||||
|
limits:
|
||||||
|
properties:
|
||||||
|
cpu:
|
||||||
|
anyOf:
|
||||||
|
- type: integer
|
||||||
|
- type: string
|
||||||
|
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||||
|
x-kubernetes-int-or-string: true
|
||||||
|
memory:
|
||||||
|
anyOf:
|
||||||
|
- type: integer
|
||||||
|
- type: string
|
||||||
|
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||||
|
x-kubernetes-int-or-string: true
|
||||||
|
type: object
|
||||||
|
requests:
|
||||||
|
properties:
|
||||||
|
cpu:
|
||||||
|
anyOf:
|
||||||
|
- type: integer
|
||||||
|
- type: string
|
||||||
|
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||||
|
x-kubernetes-int-or-string: true
|
||||||
|
memory:
|
||||||
|
anyOf:
|
||||||
|
- type: integer
|
||||||
|
- type: string
|
||||||
|
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||||
|
x-kubernetes-int-or-string: true
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
|
minimum:
|
||||||
|
properties:
|
||||||
|
limits:
|
||||||
|
properties:
|
||||||
|
cpu:
|
||||||
|
anyOf:
|
||||||
|
- type: integer
|
||||||
|
- type: string
|
||||||
|
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||||
|
x-kubernetes-int-or-string: true
|
||||||
|
memory:
|
||||||
|
anyOf:
|
||||||
|
- type: integer
|
||||||
|
- type: string
|
||||||
|
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||||
|
x-kubernetes-int-or-string: true
|
||||||
|
type: object
|
||||||
|
requests:
|
||||||
|
properties:
|
||||||
|
cpu:
|
||||||
|
anyOf:
|
||||||
|
- type: integer
|
||||||
|
- type: string
|
||||||
|
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||||
|
x-kubernetes-int-or-string: true
|
||||||
|
memory:
|
||||||
|
anyOf:
|
||||||
|
- type: integer
|
||||||
|
- type: string
|
||||||
|
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||||
|
x-kubernetes-int-or-string: true
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- controllerName
|
||||||
|
- parametersRef
|
||||||
|
type: object
|
||||||
|
x-kubernetes-validations:
|
||||||
|
- message: controllerName is immutable
|
||||||
|
rule: self.controllerName == oldSelf.controllerName
|
||||||
|
- message: parametersRef is immutable
|
||||||
|
rule: self.parametersRef == oldSelf.parametersRef
|
||||||
|
status:
|
||||||
|
properties:
|
||||||
|
conditions:
|
||||||
|
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
|
||||||
|
observedGeneration:
|
||||||
|
description: |-
|
||||||
|
observedGeneration represents the .metadata.generation that the condition was set based upon.
|
||||||
|
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
||||||
|
with respect to the current state of the instance.
|
||||||
|
format: int64
|
||||||
|
minimum: 0
|
||||||
|
type: integer
|
||||||
|
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, one of True, False, Unknown.
|
||||||
|
enum:
|
||||||
|
- "True"
|
||||||
|
- "False"
|
||||||
|
- Unknown
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||||
|
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
|
||||||
|
observedGeneration:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- spec
|
||||||
|
type: object
|
||||||
|
served: true
|
||||||
|
storage: true
|
||||||
|
subresources:
|
||||||
|
status: {}
|
||||||
@@ -0,0 +1,408 @@
|
|||||||
|
---
|
||||||
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
controller-gen.kubebuilder.io/version: v0.22.0
|
||||||
|
name: jobs.execution.ayatori.ddupan.top
|
||||||
|
spec:
|
||||||
|
group: execution.ayatori.ddupan.top
|
||||||
|
names:
|
||||||
|
kind: Job
|
||||||
|
listKind: JobList
|
||||||
|
plural: jobs
|
||||||
|
singular: job
|
||||||
|
scope: Namespaced
|
||||||
|
versions:
|
||||||
|
- additionalPrinterColumns:
|
||||||
|
- jsonPath: .status.conditions[?(@.type=='Accepted')].status
|
||||||
|
name: Accepted
|
||||||
|
type: string
|
||||||
|
- jsonPath: .status.conditions[?(@.type=='Scheduled')].status
|
||||||
|
name: Scheduled
|
||||||
|
type: string
|
||||||
|
- jsonPath: .status.conditions[?(@.type=='Succeeded')].status
|
||||||
|
name: Succeeded
|
||||||
|
type: string
|
||||||
|
- jsonPath: .metadata.creationTimestamp
|
||||||
|
name: Age
|
||||||
|
type: date
|
||||||
|
name: v1alpha1
|
||||||
|
schema:
|
||||||
|
openAPIV3Schema:
|
||||||
|
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:
|
||||||
|
properties:
|
||||||
|
activeDeadlineSeconds:
|
||||||
|
format: int64
|
||||||
|
minimum: 1
|
||||||
|
type: integer
|
||||||
|
desiredState:
|
||||||
|
default: Running
|
||||||
|
enum:
|
||||||
|
- Running
|
||||||
|
- Cancelled
|
||||||
|
type: string
|
||||||
|
jobClassName:
|
||||||
|
type: string
|
||||||
|
resources:
|
||||||
|
properties:
|
||||||
|
limits:
|
||||||
|
properties:
|
||||||
|
cpu:
|
||||||
|
anyOf:
|
||||||
|
- type: integer
|
||||||
|
- type: string
|
||||||
|
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||||
|
x-kubernetes-int-or-string: true
|
||||||
|
memory:
|
||||||
|
anyOf:
|
||||||
|
- type: integer
|
||||||
|
- type: string
|
||||||
|
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||||
|
x-kubernetes-int-or-string: true
|
||||||
|
type: object
|
||||||
|
requests:
|
||||||
|
properties:
|
||||||
|
cpu:
|
||||||
|
anyOf:
|
||||||
|
- type: integer
|
||||||
|
- type: string
|
||||||
|
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||||
|
x-kubernetes-int-or-string: true
|
||||||
|
memory:
|
||||||
|
anyOf:
|
||||||
|
- type: integer
|
||||||
|
- type: string
|
||||||
|
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||||
|
x-kubernetes-int-or-string: true
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
|
task:
|
||||||
|
properties:
|
||||||
|
args:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
command:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
env:
|
||||||
|
items:
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
pattern: ^[A-Za-z_][A-Za-z0-9_]*$
|
||||||
|
type: string
|
||||||
|
value:
|
||||||
|
type: string
|
||||||
|
valueFrom:
|
||||||
|
properties:
|
||||||
|
configMapKeyRef:
|
||||||
|
description: Selects a key from a ConfigMap.
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
description: |-
|
||||||
|
The key to select from the ConfigMap's Data field.
|
||||||
|
Keys in the BinaryData field are not currently propagated to container env vars.
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
default: ""
|
||||||
|
description: |-
|
||||||
|
Name of the referent.
|
||||||
|
This field is effectively required, but due to backwards compatibility is
|
||||||
|
allowed to be empty. Instances of this type with an empty value here are
|
||||||
|
almost certainly wrong.
|
||||||
|
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||||
|
type: string
|
||||||
|
optional:
|
||||||
|
description: Specify whether the ConfigMap or its
|
||||||
|
key must be defined
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- key
|
||||||
|
type: object
|
||||||
|
x-kubernetes-map-type: atomic
|
||||||
|
secretKeyRef:
|
||||||
|
description: SecretKeySelector selects a key of a Secret.
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
description: The key of the secret to select from. Must
|
||||||
|
be a valid secret key.
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
default: ""
|
||||||
|
description: |-
|
||||||
|
Name of the referent.
|
||||||
|
This field is effectively required, but due to backwards compatibility is
|
||||||
|
allowed to be empty. Instances of this type with an empty value here are
|
||||||
|
almost certainly wrong.
|
||||||
|
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||||
|
type: string
|
||||||
|
optional:
|
||||||
|
description: Specify whether the Secret or its key
|
||||||
|
must be defined
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- key
|
||||||
|
type: object
|
||||||
|
x-kubernetes-map-type: atomic
|
||||||
|
type: object
|
||||||
|
x-kubernetes-validations:
|
||||||
|
- message: exactly one key reference must be set
|
||||||
|
rule: has(self.secretKeyRef) != has(self.configMapKeyRef)
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
type: object
|
||||||
|
x-kubernetes-validations:
|
||||||
|
- message: exactly one of value or valueFrom must be set
|
||||||
|
rule: has(self.value) != has(self.valueFrom)
|
||||||
|
type: array
|
||||||
|
x-kubernetes-list-map-keys:
|
||||||
|
- name
|
||||||
|
x-kubernetes-list-type: map
|
||||||
|
image:
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
imagePullSecrets:
|
||||||
|
items:
|
||||||
|
description: |-
|
||||||
|
LocalObjectReference contains enough information to let you locate the
|
||||||
|
referenced object inside the same namespace.
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
default: ""
|
||||||
|
description: |-
|
||||||
|
Name of the referent.
|
||||||
|
This field is effectively required, but due to backwards compatibility is
|
||||||
|
allowed to be empty. Instances of this type with an empty value here are
|
||||||
|
almost certainly wrong.
|
||||||
|
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
x-kubernetes-map-type: atomic
|
||||||
|
type: array
|
||||||
|
workingDir:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- image
|
||||||
|
type: object
|
||||||
|
ttlSecondsAfterFinished:
|
||||||
|
format: int32
|
||||||
|
minimum: 0
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- task
|
||||||
|
type: object
|
||||||
|
x-kubernetes-validations:
|
||||||
|
- message: task is immutable
|
||||||
|
rule: self.task == oldSelf.task
|
||||||
|
- message: resources are immutable
|
||||||
|
rule: self.resources == oldSelf.resources
|
||||||
|
- message: activeDeadlineSeconds is immutable
|
||||||
|
rule: has(self.activeDeadlineSeconds) == has(oldSelf.activeDeadlineSeconds)
|
||||||
|
&& (!has(self.activeDeadlineSeconds) || self.activeDeadlineSeconds
|
||||||
|
== oldSelf.activeDeadlineSeconds)
|
||||||
|
- message: jobClassName is immutable
|
||||||
|
rule: has(self.jobClassName) == has(oldSelf.jobClassName) && (!has(self.jobClassName)
|
||||||
|
|| self.jobClassName == oldSelf.jobClassName)
|
||||||
|
- message: desiredState may only transition from Running to Cancelled
|
||||||
|
rule: oldSelf.desiredState == self.desiredState || (oldSelf.desiredState
|
||||||
|
== 'Running' && self.desiredState == 'Cancelled')
|
||||||
|
status:
|
||||||
|
properties:
|
||||||
|
completionTime:
|
||||||
|
format: date-time
|
||||||
|
type: string
|
||||||
|
conditions:
|
||||||
|
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
|
||||||
|
observedGeneration:
|
||||||
|
description: |-
|
||||||
|
observedGeneration represents the .metadata.generation that the condition was set based upon.
|
||||||
|
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
||||||
|
with respect to the current state of the instance.
|
||||||
|
format: int64
|
||||||
|
minimum: 0
|
||||||
|
type: integer
|
||||||
|
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, one of True, False, Unknown.
|
||||||
|
enum:
|
||||||
|
- "True"
|
||||||
|
- "False"
|
||||||
|
- Unknown
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||||
|
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
|
||||||
|
effectiveResources:
|
||||||
|
properties:
|
||||||
|
limits:
|
||||||
|
properties:
|
||||||
|
cpu:
|
||||||
|
anyOf:
|
||||||
|
- type: integer
|
||||||
|
- type: string
|
||||||
|
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||||
|
x-kubernetes-int-or-string: true
|
||||||
|
memory:
|
||||||
|
anyOf:
|
||||||
|
- type: integer
|
||||||
|
- type: string
|
||||||
|
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||||
|
x-kubernetes-int-or-string: true
|
||||||
|
type: object
|
||||||
|
requests:
|
||||||
|
properties:
|
||||||
|
cpu:
|
||||||
|
anyOf:
|
||||||
|
- type: integer
|
||||||
|
- type: string
|
||||||
|
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||||
|
x-kubernetes-int-or-string: true
|
||||||
|
memory:
|
||||||
|
anyOf:
|
||||||
|
- type: integer
|
||||||
|
- type: string
|
||||||
|
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||||
|
x-kubernetes-int-or-string: true
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
|
execution:
|
||||||
|
properties:
|
||||||
|
adapter:
|
||||||
|
type: string
|
||||||
|
references:
|
||||||
|
items:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- type
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
x-kubernetes-list-map-keys:
|
||||||
|
- type
|
||||||
|
x-kubernetes-list-type: map
|
||||||
|
required:
|
||||||
|
- adapter
|
||||||
|
type: object
|
||||||
|
observedGeneration:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
resolvedJobClass:
|
||||||
|
properties:
|
||||||
|
controllerName:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
parametersRef:
|
||||||
|
properties:
|
||||||
|
group:
|
||||||
|
type: string
|
||||||
|
kind:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
uid:
|
||||||
|
description: |-
|
||||||
|
UID is a type that holds unique ID values, including UUIDs. Because we
|
||||||
|
don't ONLY use UUIDs, this is an alias to string. Being a type captures
|
||||||
|
intent and helps make sure that UIDs and names do not get conflated.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- group
|
||||||
|
- kind
|
||||||
|
- name
|
||||||
|
type: object
|
||||||
|
uid:
|
||||||
|
description: |-
|
||||||
|
UID is a type that holds unique ID values, including UUIDs. Because we
|
||||||
|
don't ONLY use UUIDs, this is an alias to string. Being a type captures
|
||||||
|
intent and helps make sure that UIDs and names do not get conflated.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- controllerName
|
||||||
|
- name
|
||||||
|
- parametersRef
|
||||||
|
- uid
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
exitCode:
|
||||||
|
format: int32
|
||||||
|
type: integer
|
||||||
|
reason:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
startTime:
|
||||||
|
format: date-time
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- spec
|
||||||
|
type: object
|
||||||
|
served: true
|
||||||
|
storage: true
|
||||||
|
subresources:
|
||||||
|
status: {}
|
||||||
@@ -0,0 +1,339 @@
|
|||||||
|
---
|
||||||
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
controller-gen.kubebuilder.io/version: v0.22.0
|
||||||
|
name: kubernetesexecutionparameters.execution.ayatori.ddupan.top
|
||||||
|
spec:
|
||||||
|
group: execution.ayatori.ddupan.top
|
||||||
|
names:
|
||||||
|
kind: KubernetesExecutionParameters
|
||||||
|
listKind: KubernetesExecutionParametersList
|
||||||
|
plural: kubernetesexecutionparameters
|
||||||
|
singular: kubernetesexecutionparameters
|
||||||
|
scope: Cluster
|
||||||
|
versions:
|
||||||
|
- name: v1alpha1
|
||||||
|
schema:
|
||||||
|
openAPIV3Schema:
|
||||||
|
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:
|
||||||
|
properties:
|
||||||
|
imagePullPolicy:
|
||||||
|
default: IfNotPresent
|
||||||
|
description: PullPolicy describes a policy for if/when to pull a container
|
||||||
|
image
|
||||||
|
enum:
|
||||||
|
- Always
|
||||||
|
- Never
|
||||||
|
- IfNotPresent
|
||||||
|
type: string
|
||||||
|
podSecurityContext:
|
||||||
|
description: |-
|
||||||
|
PodSecurityContext holds pod-level security attributes and common container settings.
|
||||||
|
Some fields are also present in container.securityContext. Field values of
|
||||||
|
container.securityContext take precedence over field values of PodSecurityContext.
|
||||||
|
properties:
|
||||||
|
appArmorProfile:
|
||||||
|
description: |-
|
||||||
|
appArmorProfile is the AppArmor options to use by the containers in this pod.
|
||||||
|
Note that this field cannot be set when spec.os.name is windows.
|
||||||
|
properties:
|
||||||
|
localhostProfile:
|
||||||
|
description: |-
|
||||||
|
localhostProfile indicates a profile loaded on the node that should be used.
|
||||||
|
The profile must be preconfigured on the node to work.
|
||||||
|
Must match the loaded name of the profile.
|
||||||
|
Must be set if and only if type is "Localhost".
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: |-
|
||||||
|
type indicates which kind of AppArmor profile will be applied.
|
||||||
|
Valid options are:
|
||||||
|
Localhost - a profile pre-loaded on the node.
|
||||||
|
RuntimeDefault - the container runtime's default profile.
|
||||||
|
Unconfined - no AppArmor enforcement.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- type
|
||||||
|
type: object
|
||||||
|
fsGroup:
|
||||||
|
description: |-
|
||||||
|
A special supplemental group that applies to all containers in a pod.
|
||||||
|
Some volume types allow the Kubelet to change the ownership of that volume
|
||||||
|
to be owned by the pod:
|
||||||
|
|
||||||
|
1. The owning GID will be the FSGroup
|
||||||
|
2. The setgid bit is set (new files created in the volume will be owned by FSGroup)
|
||||||
|
3. The permission bits are OR'd with rw-rw----
|
||||||
|
|
||||||
|
If unset, the Kubelet will not modify the ownership and permissions of any volume.
|
||||||
|
Note that this field cannot be set when spec.os.name is windows.
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
fsGroupChangePolicy:
|
||||||
|
description: |-
|
||||||
|
fsGroupChangePolicy defines behavior of changing ownership and permission of the volume
|
||||||
|
before being exposed inside Pod. This field will only apply to
|
||||||
|
volume types which support fsGroup based ownership(and permissions).
|
||||||
|
It will have no effect on ephemeral volume types such as: secret, configmaps
|
||||||
|
and emptydir.
|
||||||
|
Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used.
|
||||||
|
Note that this field cannot be set when spec.os.name is windows.
|
||||||
|
type: string
|
||||||
|
runAsGroup:
|
||||||
|
description: |-
|
||||||
|
The GID to run the entrypoint of the container process.
|
||||||
|
Uses runtime default if unset.
|
||||||
|
May also be set in SecurityContext. If set in both SecurityContext and
|
||||||
|
PodSecurityContext, the value specified in SecurityContext takes precedence
|
||||||
|
for that container.
|
||||||
|
Note that this field cannot be set when spec.os.name is windows.
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
runAsNonRoot:
|
||||||
|
description: |-
|
||||||
|
Indicates that the container must run as a non-root user.
|
||||||
|
If true, the Kubelet will validate the image at runtime to ensure that it
|
||||||
|
does not run as UID 0 (root) and fail to start the container if it does.
|
||||||
|
If unset or false, no such validation will be performed.
|
||||||
|
May also be set in SecurityContext. If set in both SecurityContext and
|
||||||
|
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||||
|
type: boolean
|
||||||
|
runAsUser:
|
||||||
|
description: |-
|
||||||
|
The UID to run the entrypoint of the container process.
|
||||||
|
Defaults to user specified in image metadata if unspecified.
|
||||||
|
May also be set in SecurityContext. If set in both SecurityContext and
|
||||||
|
PodSecurityContext, the value specified in SecurityContext takes precedence
|
||||||
|
for that container.
|
||||||
|
Note that this field cannot be set when spec.os.name is windows.
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
seLinuxChangePolicy:
|
||||||
|
description: |-
|
||||||
|
seLinuxChangePolicy defines how the container's SELinux label is applied to all volumes used by the Pod.
|
||||||
|
It has no effect on nodes that do not support SELinux or to volumes does not support SELinux.
|
||||||
|
Valid values are "MountOption" and "Recursive".
|
||||||
|
|
||||||
|
"Recursive" means relabeling of all files on all Pod volumes by the container runtime.
|
||||||
|
This may be slow for large volumes, but allows mixing privileged and unprivileged Pods sharing the same volume on the same node.
|
||||||
|
|
||||||
|
"MountOption" mounts all eligible Pod volumes with `-o context` mount option.
|
||||||
|
This requires all Pods that share the same volume to use the same SELinux label.
|
||||||
|
It is not possible to share the same volume among privileged and unprivileged Pods.
|
||||||
|
Eligible volumes are in-tree FibreChannel and iSCSI volumes, and all CSI volumes
|
||||||
|
whose CSI driver announces SELinux support by setting spec.seLinuxMount: true in their
|
||||||
|
CSIDriver instance. Other volumes are always re-labelled recursively.
|
||||||
|
|
||||||
|
If not specified, "MountOption" is used.
|
||||||
|
|
||||||
|
This field affects only Pods that have SELinux label set, either in PodSecurityContext or in SecurityContext of all containers.
|
||||||
|
|
||||||
|
All Pods that use the same volume should use the same seLinuxChangePolicy, otherwise some pods can get stuck in ContainerCreating state.
|
||||||
|
Note that this field cannot be set when spec.os.name is windows.
|
||||||
|
type: string
|
||||||
|
seLinuxOptions:
|
||||||
|
description: |-
|
||||||
|
The SELinux context to be applied to all containers.
|
||||||
|
If unspecified, the container runtime will allocate a random SELinux context for each
|
||||||
|
container. May also be set in SecurityContext. If set in
|
||||||
|
both SecurityContext and PodSecurityContext, the value specified in SecurityContext
|
||||||
|
takes precedence for that container.
|
||||||
|
Note that this field cannot be set when spec.os.name is windows.
|
||||||
|
properties:
|
||||||
|
level:
|
||||||
|
description: Level is SELinux level label that applies to
|
||||||
|
the container.
|
||||||
|
type: string
|
||||||
|
role:
|
||||||
|
description: Role is a SELinux role label that applies to
|
||||||
|
the container.
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: Type is a SELinux type label that applies to
|
||||||
|
the container.
|
||||||
|
type: string
|
||||||
|
user:
|
||||||
|
description: User is a SELinux user label that applies to
|
||||||
|
the container.
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
seccompProfile:
|
||||||
|
description: |-
|
||||||
|
The seccomp options to use by the containers in this pod.
|
||||||
|
Note that this field cannot be set when spec.os.name is windows.
|
||||||
|
properties:
|
||||||
|
localhostProfile:
|
||||||
|
description: |-
|
||||||
|
localhostProfile indicates a profile defined in a file on the node should be used.
|
||||||
|
The profile must be preconfigured on the node to work.
|
||||||
|
Must be a descending path, relative to the kubelet's configured seccomp profile location.
|
||||||
|
Must be set if type is "Localhost". Must NOT be set for any other type.
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: |-
|
||||||
|
type indicates which kind of seccomp profile will be applied.
|
||||||
|
Valid options are:
|
||||||
|
|
||||||
|
Localhost - a profile defined in a file on the node should be used.
|
||||||
|
RuntimeDefault - the container runtime default profile should be used.
|
||||||
|
Unconfined - no profile should be applied.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- type
|
||||||
|
type: object
|
||||||
|
supplementalGroups:
|
||||||
|
description: |-
|
||||||
|
A list of groups applied to the first process run in each container, in
|
||||||
|
addition to the container's primary GID and fsGroup (if specified). If
|
||||||
|
the SupplementalGroupsPolicy feature is enabled, the
|
||||||
|
supplementalGroupsPolicy field determines whether these are in addition
|
||||||
|
to or instead of any group memberships defined in the container image.
|
||||||
|
If unspecified, no additional groups are added, though group memberships
|
||||||
|
defined in the container image may still be used, depending on the
|
||||||
|
supplementalGroupsPolicy field.
|
||||||
|
Note that this field cannot be set when spec.os.name is windows.
|
||||||
|
items:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
|
x-kubernetes-list-type: atomic
|
||||||
|
supplementalGroupsPolicy:
|
||||||
|
description: |-
|
||||||
|
Defines how supplemental groups of the first container processes are calculated.
|
||||||
|
Valid values are "Merge" and "Strict". If not specified, "Merge" is used.
|
||||||
|
(Alpha) Using the field requires the SupplementalGroupsPolicy feature gate to be enabled
|
||||||
|
and the container runtime must implement support for this feature.
|
||||||
|
Note that this field cannot be set when spec.os.name is windows.
|
||||||
|
type: string
|
||||||
|
sysctls:
|
||||||
|
description: |-
|
||||||
|
Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported
|
||||||
|
sysctls (by the container runtime) might fail to launch.
|
||||||
|
Note that this field cannot be set when spec.os.name is windows.
|
||||||
|
items:
|
||||||
|
description: Sysctl defines a kernel parameter to be set
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
description: Name of a property to set
|
||||||
|
type: string
|
||||||
|
value:
|
||||||
|
description: Value of a property to set
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- value
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
x-kubernetes-list-type: atomic
|
||||||
|
windowsOptions:
|
||||||
|
description: |-
|
||||||
|
The Windows specific settings applied to all containers.
|
||||||
|
If unspecified, the options within a container's SecurityContext will be used.
|
||||||
|
If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||||
|
Note that this field cannot be set when spec.os.name is linux.
|
||||||
|
properties:
|
||||||
|
gmsaCredentialSpec:
|
||||||
|
description: |-
|
||||||
|
GMSACredentialSpec is where the GMSA admission webhook
|
||||||
|
(https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the
|
||||||
|
GMSA credential spec named by the GMSACredentialSpecName field.
|
||||||
|
type: string
|
||||||
|
gmsaCredentialSpecName:
|
||||||
|
description: GMSACredentialSpecName is the name of the GMSA
|
||||||
|
credential spec to use.
|
||||||
|
type: string
|
||||||
|
hostProcess:
|
||||||
|
description: |-
|
||||||
|
HostProcess determines if a container should be run as a 'Host Process' container.
|
||||||
|
All of a Pod's containers must have the same effective HostProcess value
|
||||||
|
(it is not allowed to have a mix of HostProcess containers and non-HostProcess containers).
|
||||||
|
In addition, if HostProcess is true then HostNetwork must also be set to true.
|
||||||
|
type: boolean
|
||||||
|
runAsUserName:
|
||||||
|
description: |-
|
||||||
|
The UserName in Windows to run the entrypoint of the container process.
|
||||||
|
Defaults to the user specified in image metadata if unspecified.
|
||||||
|
May also be set in PodSecurityContext. If set in both SecurityContext and
|
||||||
|
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
|
runtimeClassName:
|
||||||
|
type: string
|
||||||
|
scheduling:
|
||||||
|
properties:
|
||||||
|
nodeSelector:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
tolerations:
|
||||||
|
items:
|
||||||
|
description: |-
|
||||||
|
The pod this Toleration is attached to tolerates any taint that matches
|
||||||
|
the triple <key,value,effect> using the matching operator <operator>.
|
||||||
|
properties:
|
||||||
|
effect:
|
||||||
|
description: |-
|
||||||
|
Effect indicates the taint effect to match. Empty means match all taint effects.
|
||||||
|
When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.
|
||||||
|
type: string
|
||||||
|
key:
|
||||||
|
description: |-
|
||||||
|
Key is the taint key that the toleration applies to. Empty means match all taint keys.
|
||||||
|
If the key is empty, operator must be Exists; this combination means to match all values and all keys.
|
||||||
|
type: string
|
||||||
|
operator:
|
||||||
|
description: |-
|
||||||
|
Operator represents a key's relationship to the value.
|
||||||
|
Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal.
|
||||||
|
Exists is equivalent to wildcard for value, so that a pod can
|
||||||
|
tolerate all taints of a particular category.
|
||||||
|
Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators).
|
||||||
|
type: string
|
||||||
|
tolerationSeconds:
|
||||||
|
description: |-
|
||||||
|
TolerationSeconds represents the period of time the toleration (which must be
|
||||||
|
of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default,
|
||||||
|
it is not set, which means tolerate the taint forever (do not evict). Zero and
|
||||||
|
negative values will be treated as 0 (evict immediately) by the system.
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
value:
|
||||||
|
description: |-
|
||||||
|
Value is the taint value the toleration matches to.
|
||||||
|
If the operator is Exists, the value should be empty, otherwise just a regular string.
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
serviceAccountName:
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- serviceAccountName
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- spec
|
||||||
|
type: object
|
||||||
|
served: true
|
||||||
|
storage: true
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
---
|
||||||
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
controller-gen.kubebuilder.io/version: v0.22.0
|
||||||
|
name: opensandboxexecutionparameters.execution.ayatori.ddupan.top
|
||||||
|
spec:
|
||||||
|
group: execution.ayatori.ddupan.top
|
||||||
|
names:
|
||||||
|
kind: OpenSandboxExecutionParameters
|
||||||
|
listKind: OpenSandboxExecutionParametersList
|
||||||
|
plural: opensandboxexecutionparameters
|
||||||
|
singular: opensandboxexecutionparameters
|
||||||
|
scope: Cluster
|
||||||
|
versions:
|
||||||
|
- name: v1alpha1
|
||||||
|
schema:
|
||||||
|
openAPIV3Schema:
|
||||||
|
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:
|
||||||
|
properties:
|
||||||
|
allowImageAuth:
|
||||||
|
type: boolean
|
||||||
|
allowInsecureHTTP:
|
||||||
|
description: AllowInsecureHTTP is intended for isolated development
|
||||||
|
environments only.
|
||||||
|
type: boolean
|
||||||
|
allowSecretEnv:
|
||||||
|
type: boolean
|
||||||
|
apiKeySecretRef:
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
namespace:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- key
|
||||||
|
- name
|
||||||
|
- namespace
|
||||||
|
type: object
|
||||||
|
endpoint:
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
poolRef:
|
||||||
|
type: string
|
||||||
|
requestMapping:
|
||||||
|
default: AdmissionOnly
|
||||||
|
enum:
|
||||||
|
- AdmissionOnly
|
||||||
|
- Native
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- apiKeySecretRef
|
||||||
|
- endpoint
|
||||||
|
type: object
|
||||||
|
x-kubernetes-validations:
|
||||||
|
- message: endpoint must use HTTPS unless allowInsecureHTTP is true
|
||||||
|
rule: self.allowInsecureHTTP || self.endpoint.startsWith('https://')
|
||||||
|
required:
|
||||||
|
- spec
|
||||||
|
type: object
|
||||||
|
served: true
|
||||||
|
storage: true
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# This kustomization.yaml is not intended to be run by itself,
|
||||||
|
# since it depends on service name and namespace that are out of this kustomize package.
|
||||||
|
# It should be run by config/default
|
||||||
|
resources:
|
||||||
|
- bases/execution.ayatori.ddupan.top_jobs.yaml
|
||||||
|
- bases/execution.ayatori.ddupan.top_jobclasses.yaml
|
||||||
|
- bases/execution.ayatori.ddupan.top_kubernetesexecutionparameters.yaml
|
||||||
|
- bases/execution.ayatori.ddupan.top_opensandboxexecutionparameters.yaml
|
||||||
|
# +kubebuilder:scaffold:crdkustomizeresource
|
||||||
|
|
||||||
|
patches:
|
||||||
|
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
|
||||||
|
# patches here are for enabling the conversion webhook for each CRD
|
||||||
|
# +kubebuilder:scaffold:crdkustomizewebhookpatch
|
||||||
|
|
||||||
|
# [WEBHOOK] To enable webhook, uncomment the following section
|
||||||
|
# the following config is for teaching kustomize how to do kustomization for CRDs.
|
||||||
|
#configurations:
|
||||||
|
#- kustomizeconfig.yaml
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# This file is for teaching kustomize how to substitute name and namespace reference in CRD
|
||||||
|
nameReference:
|
||||||
|
- kind: Service
|
||||||
|
version: v1
|
||||||
|
fieldSpecs:
|
||||||
|
- kind: CustomResourceDefinition
|
||||||
|
version: v1
|
||||||
|
group: apiextensions.k8s.io
|
||||||
|
path: spec/conversion/webhook/clientConfig/service/name
|
||||||
|
|
||||||
|
varReference:
|
||||||
|
- path: metadata/annotations
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# This patch adds the args, volumes, and ports to allow the manager to use the metrics-server certs.
|
||||||
|
|
||||||
|
# Add the volumeMount for the metrics-server certs
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/containers/0/volumeMounts/-
|
||||||
|
value:
|
||||||
|
mountPath: /tmp/k8s-metrics-server/metrics-certs
|
||||||
|
name: metrics-certs
|
||||||
|
readOnly: true
|
||||||
|
|
||||||
|
# Add the --metrics-cert-path argument for the metrics server
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/containers/0/args/-
|
||||||
|
value: --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs
|
||||||
|
|
||||||
|
# Add the metrics-server certs volume configuration
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/volumes/-
|
||||||
|
value:
|
||||||
|
name: metrics-certs
|
||||||
|
secret:
|
||||||
|
secretName: metrics-server-cert
|
||||||
|
optional: false
|
||||||
|
items:
|
||||||
|
- key: ca.crt
|
||||||
|
path: ca.crt
|
||||||
|
- key: tls.crt
|
||||||
|
path: tls.crt
|
||||||
|
- key: tls.key
|
||||||
|
path: tls.key
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
# Adds namespace to all resources.
|
||||||
|
namespace: ayatori-system
|
||||||
|
|
||||||
|
# Value of this field is prepended to the
|
||||||
|
# names of all resources, e.g. a deployment named
|
||||||
|
# "wordpress" becomes "alices-wordpress".
|
||||||
|
# Note that it should also match with the prefix (text before '-') of the namespace
|
||||||
|
# field above.
|
||||||
|
namePrefix: ayatori-
|
||||||
|
|
||||||
|
# Labels to add to all resources and selectors.
|
||||||
|
#labels:
|
||||||
|
#- includeSelectors: true
|
||||||
|
# pairs:
|
||||||
|
# someName: someValue
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- ../crd
|
||||||
|
- ../rbac
|
||||||
|
- ../manager
|
||||||
|
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
|
||||||
|
# crd/kustomization.yaml
|
||||||
|
#- ../webhook
|
||||||
|
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.
|
||||||
|
#- ../certmanager
|
||||||
|
# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.
|
||||||
|
#- ../prometheus
|
||||||
|
# [METRICS] Expose the controller manager metrics service.
|
||||||
|
- metrics_service.yaml
|
||||||
|
# [NETWORK POLICY] Control ingress to metrics and webhook ports.
|
||||||
|
# Allow metrics traffic from pods in namespaces labeled 'metrics: enabled'.
|
||||||
|
# Allow webhook traffic from all sources.
|
||||||
|
#- ../network-policy
|
||||||
|
|
||||||
|
# Uncomment the patches line if you enable Metrics
|
||||||
|
patches:
|
||||||
|
# [METRICS] The following patch will enable the metrics endpoint using HTTPS and the port :8443.
|
||||||
|
# More info: https://book.kubebuilder.io/reference/metrics
|
||||||
|
- path: manager_metrics_patch.yaml
|
||||||
|
target:
|
||||||
|
kind: Deployment
|
||||||
|
|
||||||
|
# Uncomment the patches line if you enable Metrics and CertManager
|
||||||
|
# [METRICS-WITH-CERTS] To enable metrics protected with certManager, uncomment the following line.
|
||||||
|
# This patch will protect the metrics with certManager self-signed certs.
|
||||||
|
#- path: cert_metrics_manager_patch.yaml
|
||||||
|
# target:
|
||||||
|
# kind: Deployment
|
||||||
|
|
||||||
|
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
|
||||||
|
# crd/kustomization.yaml
|
||||||
|
#- path: manager_webhook_patch.yaml
|
||||||
|
# target:
|
||||||
|
# kind: Deployment
|
||||||
|
|
||||||
|
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.
|
||||||
|
# Uncomment the following replacements to add the cert-manager CA injection annotations
|
||||||
|
#replacements:
|
||||||
|
# - source: # Uncomment the following block to enable certificates for metrics
|
||||||
|
# kind: Service
|
||||||
|
# version: v1
|
||||||
|
# name: controller-manager-metrics-service
|
||||||
|
# fieldPath: metadata.name
|
||||||
|
# targets:
|
||||||
|
# - select:
|
||||||
|
# kind: Certificate
|
||||||
|
# group: cert-manager.io
|
||||||
|
# version: v1
|
||||||
|
# name: metrics-certs
|
||||||
|
# fieldPaths:
|
||||||
|
# - spec.dnsNames.0
|
||||||
|
# - spec.dnsNames.1
|
||||||
|
# options:
|
||||||
|
# delimiter: '.'
|
||||||
|
# index: 0
|
||||||
|
# create: true
|
||||||
|
# - select: # Uncomment the following to set the Service name for TLS config in Prometheus ServiceMonitor
|
||||||
|
# kind: ServiceMonitor
|
||||||
|
# group: monitoring.coreos.com
|
||||||
|
# version: v1
|
||||||
|
# name: controller-manager-metrics-monitor
|
||||||
|
# fieldPaths:
|
||||||
|
# - spec.endpoints.0.tlsConfig.serverName
|
||||||
|
# options:
|
||||||
|
# delimiter: '.'
|
||||||
|
# index: 0
|
||||||
|
# create: true
|
||||||
|
|
||||||
|
# - source:
|
||||||
|
# kind: Service
|
||||||
|
# version: v1
|
||||||
|
# name: controller-manager-metrics-service
|
||||||
|
# fieldPath: metadata.namespace
|
||||||
|
# targets:
|
||||||
|
# - select:
|
||||||
|
# kind: Certificate
|
||||||
|
# group: cert-manager.io
|
||||||
|
# version: v1
|
||||||
|
# name: metrics-certs
|
||||||
|
# fieldPaths:
|
||||||
|
# - spec.dnsNames.0
|
||||||
|
# - spec.dnsNames.1
|
||||||
|
# options:
|
||||||
|
# delimiter: '.'
|
||||||
|
# index: 1
|
||||||
|
# create: true
|
||||||
|
# - select: # Uncomment the following to set the Service namespace for TLS in Prometheus ServiceMonitor
|
||||||
|
# kind: ServiceMonitor
|
||||||
|
# group: monitoring.coreos.com
|
||||||
|
# version: v1
|
||||||
|
# name: controller-manager-metrics-monitor
|
||||||
|
# fieldPaths:
|
||||||
|
# - spec.endpoints.0.tlsConfig.serverName
|
||||||
|
# options:
|
||||||
|
# delimiter: '.'
|
||||||
|
# index: 1
|
||||||
|
# create: true
|
||||||
|
|
||||||
|
# - source: # Uncomment the following block if you have any webhook
|
||||||
|
# kind: Service
|
||||||
|
# version: v1
|
||||||
|
# name: webhook-service
|
||||||
|
# fieldPath: .metadata.name # Name of the service
|
||||||
|
# targets:
|
||||||
|
# - select:
|
||||||
|
# kind: Certificate
|
||||||
|
# group: cert-manager.io
|
||||||
|
# version: v1
|
||||||
|
# name: serving-cert
|
||||||
|
# fieldPaths:
|
||||||
|
# - .spec.dnsNames.0
|
||||||
|
# - .spec.dnsNames.1
|
||||||
|
# options:
|
||||||
|
# delimiter: '.'
|
||||||
|
# index: 0
|
||||||
|
# create: true
|
||||||
|
# - source:
|
||||||
|
# kind: Service
|
||||||
|
# version: v1
|
||||||
|
# name: webhook-service
|
||||||
|
# fieldPath: .metadata.namespace # Namespace of the service
|
||||||
|
# targets:
|
||||||
|
# - select:
|
||||||
|
# kind: Certificate
|
||||||
|
# group: cert-manager.io
|
||||||
|
# version: v1
|
||||||
|
# name: serving-cert
|
||||||
|
# fieldPaths:
|
||||||
|
# - .spec.dnsNames.0
|
||||||
|
# - .spec.dnsNames.1
|
||||||
|
# options:
|
||||||
|
# delimiter: '.'
|
||||||
|
# index: 1
|
||||||
|
# create: true
|
||||||
|
|
||||||
|
# - source: # Uncomment the following block if you have a ValidatingWebhook (--programmatic-validation)
|
||||||
|
# kind: Certificate
|
||||||
|
# group: cert-manager.io
|
||||||
|
# version: v1
|
||||||
|
# name: serving-cert # This name should match the one in certificate.yaml
|
||||||
|
# fieldPath: .metadata.namespace # Namespace of the certificate CR
|
||||||
|
# targets:
|
||||||
|
# - select:
|
||||||
|
# kind: ValidatingWebhookConfiguration
|
||||||
|
# fieldPaths:
|
||||||
|
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
|
||||||
|
# options:
|
||||||
|
# delimiter: '/'
|
||||||
|
# index: 0
|
||||||
|
# create: true
|
||||||
|
# - source:
|
||||||
|
# kind: Certificate
|
||||||
|
# group: cert-manager.io
|
||||||
|
# version: v1
|
||||||
|
# name: serving-cert
|
||||||
|
# fieldPath: .metadata.name
|
||||||
|
# targets:
|
||||||
|
# - select:
|
||||||
|
# kind: ValidatingWebhookConfiguration
|
||||||
|
# fieldPaths:
|
||||||
|
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
|
||||||
|
# options:
|
||||||
|
# delimiter: '/'
|
||||||
|
# index: 1
|
||||||
|
# create: true
|
||||||
|
|
||||||
|
# - source: # Uncomment the following block if you have a DefaultingWebhook (--defaulting )
|
||||||
|
# kind: Certificate
|
||||||
|
# group: cert-manager.io
|
||||||
|
# version: v1
|
||||||
|
# name: serving-cert
|
||||||
|
# fieldPath: .metadata.namespace # Namespace of the certificate CR
|
||||||
|
# targets:
|
||||||
|
# - select:
|
||||||
|
# kind: MutatingWebhookConfiguration
|
||||||
|
# fieldPaths:
|
||||||
|
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
|
||||||
|
# options:
|
||||||
|
# delimiter: '/'
|
||||||
|
# index: 0
|
||||||
|
# create: true
|
||||||
|
# - source:
|
||||||
|
# kind: Certificate
|
||||||
|
# group: cert-manager.io
|
||||||
|
# version: v1
|
||||||
|
# name: serving-cert
|
||||||
|
# fieldPath: .metadata.name
|
||||||
|
# targets:
|
||||||
|
# - select:
|
||||||
|
# kind: MutatingWebhookConfiguration
|
||||||
|
# fieldPaths:
|
||||||
|
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
|
||||||
|
# options:
|
||||||
|
# delimiter: '/'
|
||||||
|
# index: 1
|
||||||
|
# create: true
|
||||||
|
|
||||||
|
# - source: # Uncomment the following block if you have a ConversionWebhook (--conversion)
|
||||||
|
# kind: Certificate
|
||||||
|
# group: cert-manager.io
|
||||||
|
# version: v1
|
||||||
|
# name: serving-cert
|
||||||
|
# fieldPath: .metadata.namespace # Namespace of the certificate CR
|
||||||
|
# targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.
|
||||||
|
# +kubebuilder:scaffold:crdkustomizecainjectionns
|
||||||
|
# - source:
|
||||||
|
# kind: Certificate
|
||||||
|
# group: cert-manager.io
|
||||||
|
# version: v1
|
||||||
|
# name: serving-cert
|
||||||
|
# fieldPath: .metadata.name
|
||||||
|
# targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.
|
||||||
|
# +kubebuilder:scaffold:crdkustomizecainjectionname
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# This patch adds the args to allow exposing the metrics endpoint using HTTPS
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/containers/0/args/0
|
||||||
|
value: --metrics-bind-address=:8443
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
control-plane: controller-manager
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: controller-manager-metrics-service
|
||||||
|
namespace: system
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: https
|
||||||
|
port: 8443
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 8443
|
||||||
|
selector:
|
||||||
|
control-plane: controller-manager
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
resources:
|
||||||
|
- manager.yaml
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
control-plane: controller-manager
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: system
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: controller-manager
|
||||||
|
namespace: system
|
||||||
|
labels:
|
||||||
|
control-plane: controller-manager
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
control-plane: controller-manager
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
kubectl.kubernetes.io/default-container: manager
|
||||||
|
labels:
|
||||||
|
control-plane: controller-manager
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
spec:
|
||||||
|
# TODO(user): Uncomment the following code to configure the nodeAffinity expression
|
||||||
|
# according to the platforms which are supported by your solution.
|
||||||
|
# It is considered best practice to support multiple architectures. You can
|
||||||
|
# build your manager image using the makefile target docker-buildx.
|
||||||
|
# affinity:
|
||||||
|
# nodeAffinity:
|
||||||
|
# requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
# nodeSelectorTerms:
|
||||||
|
# - matchExpressions:
|
||||||
|
# - key: kubernetes.io/arch
|
||||||
|
# operator: In
|
||||||
|
# values:
|
||||||
|
# - amd64
|
||||||
|
# - arm64
|
||||||
|
# - ppc64le
|
||||||
|
# - s390x
|
||||||
|
# - key: kubernetes.io/os
|
||||||
|
# operator: In
|
||||||
|
# values:
|
||||||
|
# - linux
|
||||||
|
securityContext:
|
||||||
|
# Projects are configured by default to adhere to the "restricted" Pod Security Standards.
|
||||||
|
# This ensures that deployments meet the highest security requirements for Kubernetes.
|
||||||
|
# For more details, see: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted
|
||||||
|
runAsNonRoot: true
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- command:
|
||||||
|
- /manager
|
||||||
|
args:
|
||||||
|
- --leader-elect
|
||||||
|
- --health-probe-bind-address=:8081
|
||||||
|
image: controller:latest
|
||||||
|
name: manager
|
||||||
|
ports:
|
||||||
|
- containerPort: 8081
|
||||||
|
name: health
|
||||||
|
protocol: TCP
|
||||||
|
securityContext:
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- "ALL"
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 8081
|
||||||
|
initialDelaySeconds: 15
|
||||||
|
periodSeconds: 20
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /readyz
|
||||||
|
port: 8081
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
# TODO(user): Configure the resources accordingly based on the project requirements.
|
||||||
|
# More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 128Mi
|
||||||
|
requests:
|
||||||
|
cpu: 10m
|
||||||
|
memory: 64Mi
|
||||||
|
volumeMounts: []
|
||||||
|
volumes: []
|
||||||
|
serviceAccountName: controller-manager
|
||||||
|
terminationGracePeriodSeconds: 10
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Allow metrics traffic from pods in namespaces labeled 'metrics: enabled'.
|
||||||
|
# Add this label to namespaces whose pods should scrape metrics.
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: allow-metrics-traffic
|
||||||
|
namespace: system
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
control-plane: controller-manager
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
policyTypes:
|
||||||
|
- Ingress
|
||||||
|
ingress:
|
||||||
|
# Allow pods in namespaces labeled 'metrics: enabled' to scrape metrics.
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
metrics: enabled # Only from namespaces with this label
|
||||||
|
ports:
|
||||||
|
- port: 8443
|
||||||
|
protocol: TCP
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
resources:
|
||||||
|
- allow-metrics-traffic.yaml
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
resources:
|
||||||
|
- monitor.yaml
|
||||||
|
|
||||||
|
# [PROMETHEUS-WITH-CERTS] The following patch configures the ServiceMonitor in ../prometheus
|
||||||
|
# to securely reference certificates created and managed by cert-manager.
|
||||||
|
# Additionally, ensure that you uncomment the [METRICS WITH CERTMANAGER] patch under config/default/kustomization.yaml
|
||||||
|
# to mount the "metrics-server-cert" secret in the Manager Deployment.
|
||||||
|
#patches:
|
||||||
|
# - path: monitor_tls_patch.yaml
|
||||||
|
# target:
|
||||||
|
# kind: ServiceMonitor
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Prometheus Monitor Service (Metrics)
|
||||||
|
apiVersion: monitoring.coreos.com/v1
|
||||||
|
kind: ServiceMonitor
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
control-plane: controller-manager
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: controller-manager-metrics-monitor
|
||||||
|
namespace: system
|
||||||
|
spec:
|
||||||
|
endpoints:
|
||||||
|
- path: /metrics
|
||||||
|
port: https # Ensure this is the name of the port that exposes HTTPS metrics
|
||||||
|
scheme: https
|
||||||
|
bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||||
|
tlsConfig:
|
||||||
|
# TODO(user): The option insecureSkipVerify: true is not recommended for production since it disables
|
||||||
|
# certificate verification, exposing the system to potential man-in-the-middle attacks.
|
||||||
|
# For production environments, it is recommended to use cert-manager for automatic TLS certificate management.
|
||||||
|
# To apply this configuration, enable cert-manager and use the patch located at config/prometheus/servicemonitor_tls_patch.yaml,
|
||||||
|
# which securely references the certificate from the 'metrics-server-cert' secret.
|
||||||
|
insecureSkipVerify: true
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
control-plane: controller-manager
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Patch for Prometheus ServiceMonitor to enable secure TLS configuration
|
||||||
|
# using certificates managed by cert-manager
|
||||||
|
- op: replace
|
||||||
|
path: /spec/endpoints/0/tlsConfig
|
||||||
|
value:
|
||||||
|
# SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize
|
||||||
|
serverName: SERVICE_NAME.SERVICE_NAMESPACE.svc
|
||||||
|
insecureSkipVerify: false
|
||||||
|
ca:
|
||||||
|
secret:
|
||||||
|
name: metrics-server-cert
|
||||||
|
key: ca.crt
|
||||||
|
cert:
|
||||||
|
secret:
|
||||||
|
name: metrics-server-cert
|
||||||
|
key: tls.crt
|
||||||
|
keySecret:
|
||||||
|
name: metrics-server-cert
|
||||||
|
key: tls.key
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# This rule is not used by the project ayatori itself.
|
||||||
|
# It is provided to allow the cluster admin to help manage permissions for users.
|
||||||
|
#
|
||||||
|
# Grants full permissions ('*') over execution.ayatori.ddupan.top.
|
||||||
|
# This role is intended for users authorized to modify roles and bindings within the cluster,
|
||||||
|
# enabling them to delegate specific permissions to other users or groups as needed.
|
||||||
|
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: execution-job-admin-role
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- jobs
|
||||||
|
verbs:
|
||||||
|
- '*'
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- jobs/status
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# This rule is not used by the project ayatori itself.
|
||||||
|
# It is provided to allow the cluster admin to help manage permissions for users.
|
||||||
|
#
|
||||||
|
# Grants permissions to create, update, and delete resources within the execution.ayatori.ddupan.top.
|
||||||
|
# This role is intended for users who need to manage these resources
|
||||||
|
# but should not control RBAC or manage permissions for others.
|
||||||
|
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: execution-job-editor-role
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- jobs
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- patch
|
||||||
|
- update
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- jobs/status
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# This rule is not used by the project ayatori itself.
|
||||||
|
# It is provided to allow the cluster admin to help manage permissions for users.
|
||||||
|
#
|
||||||
|
# Grants read-only access to execution.ayatori.ddupan.top resources.
|
||||||
|
# This role is intended for users who need visibility into these resources
|
||||||
|
# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.
|
||||||
|
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: execution-job-viewer-role
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- jobs
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- jobs/status
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# This rule is not used by the project ayatori itself.
|
||||||
|
# It is provided to allow the cluster admin to help manage permissions for users.
|
||||||
|
#
|
||||||
|
# Grants full permissions ('*') over execution.ayatori.ddupan.top.
|
||||||
|
# This role is intended for users authorized to modify roles and bindings within the cluster,
|
||||||
|
# enabling them to delegate specific permissions to other users or groups as needed.
|
||||||
|
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: execution-jobclass-admin-role
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- jobclasses
|
||||||
|
verbs:
|
||||||
|
- '*'
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- jobclasses/status
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# This rule is not used by the project ayatori itself.
|
||||||
|
# It is provided to allow the cluster admin to help manage permissions for users.
|
||||||
|
#
|
||||||
|
# Grants permissions to create, update, and delete resources within the execution.ayatori.ddupan.top.
|
||||||
|
# This role is intended for users who need to manage these resources
|
||||||
|
# but should not control RBAC or manage permissions for others.
|
||||||
|
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: execution-jobclass-editor-role
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- jobclasses
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- patch
|
||||||
|
- update
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- jobclasses/status
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# This rule is not used by the project ayatori itself.
|
||||||
|
# It is provided to allow the cluster admin to help manage permissions for users.
|
||||||
|
#
|
||||||
|
# Grants read-only access to execution.ayatori.ddupan.top resources.
|
||||||
|
# This role is intended for users who need visibility into these resources
|
||||||
|
# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.
|
||||||
|
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: execution-jobclass-viewer-role
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- jobclasses
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- jobclasses/status
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# This rule is not used by the project ayatori itself.
|
||||||
|
# It is provided to allow the cluster admin to help manage permissions for users.
|
||||||
|
#
|
||||||
|
# Grants full permissions ('*') over execution.ayatori.ddupan.top.
|
||||||
|
# This role is intended for users authorized to modify roles and bindings within the cluster,
|
||||||
|
# enabling them to delegate specific permissions to other users or groups as needed.
|
||||||
|
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: execution-kubernetesexecutionparameters-admin-role
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- kubernetesexecutionparameters
|
||||||
|
verbs:
|
||||||
|
- '*'
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- kubernetesexecutionparameters/status
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# This rule is not used by the project ayatori itself.
|
||||||
|
# It is provided to allow the cluster admin to help manage permissions for users.
|
||||||
|
#
|
||||||
|
# Grants permissions to create, update, and delete resources within the execution.ayatori.ddupan.top.
|
||||||
|
# This role is intended for users who need to manage these resources
|
||||||
|
# but should not control RBAC or manage permissions for others.
|
||||||
|
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: execution-kubernetesexecutionparameters-editor-role
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- kubernetesexecutionparameters
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- patch
|
||||||
|
- update
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- kubernetesexecutionparameters/status
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# This rule is not used by the project ayatori itself.
|
||||||
|
# It is provided to allow the cluster admin to help manage permissions for users.
|
||||||
|
#
|
||||||
|
# Grants read-only access to execution.ayatori.ddupan.top resources.
|
||||||
|
# This role is intended for users who need visibility into these resources
|
||||||
|
# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.
|
||||||
|
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: execution-kubernetesexecutionparameters-viewer-role
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- kubernetesexecutionparameters
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- kubernetesexecutionparameters/status
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# This rule is not used by the project ayatori itself.
|
||||||
|
# It is provided to allow the cluster admin to help manage permissions for users.
|
||||||
|
#
|
||||||
|
# Grants full permissions ('*') over execution.ayatori.ddupan.top.
|
||||||
|
# This role is intended for users authorized to modify roles and bindings within the cluster,
|
||||||
|
# enabling them to delegate specific permissions to other users or groups as needed.
|
||||||
|
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: execution-opensandboxexecutionparameters-admin-role
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- opensandboxexecutionparameters
|
||||||
|
verbs:
|
||||||
|
- '*'
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- opensandboxexecutionparameters/status
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# This rule is not used by the project ayatori itself.
|
||||||
|
# It is provided to allow the cluster admin to help manage permissions for users.
|
||||||
|
#
|
||||||
|
# Grants permissions to create, update, and delete resources within the execution.ayatori.ddupan.top.
|
||||||
|
# This role is intended for users who need to manage these resources
|
||||||
|
# but should not control RBAC or manage permissions for others.
|
||||||
|
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: execution-opensandboxexecutionparameters-editor-role
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- opensandboxexecutionparameters
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- patch
|
||||||
|
- update
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- opensandboxexecutionparameters/status
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# This rule is not used by the project ayatori itself.
|
||||||
|
# It is provided to allow the cluster admin to help manage permissions for users.
|
||||||
|
#
|
||||||
|
# Grants read-only access to execution.ayatori.ddupan.top resources.
|
||||||
|
# This role is intended for users who need visibility into these resources
|
||||||
|
# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.
|
||||||
|
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: execution-opensandboxexecutionparameters-viewer-role
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- opensandboxexecutionparameters
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- execution.ayatori.ddupan.top
|
||||||
|
resources:
|
||||||
|
- opensandboxexecutionparameters/status
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
resources:
|
||||||
|
# All RBAC will be applied under this service account in
|
||||||
|
# the deployment namespace. You may comment out this resource
|
||||||
|
# if your manager will use a service account that exists at
|
||||||
|
# runtime. Be sure to update RoleBinding and ClusterRoleBinding
|
||||||
|
# subjects if changing service account names.
|
||||||
|
- service_account.yaml
|
||||||
|
- role.yaml
|
||||||
|
- role_binding.yaml
|
||||||
|
- leader_election_role.yaml
|
||||||
|
- leader_election_role_binding.yaml
|
||||||
|
# The following RBAC configurations are used to protect
|
||||||
|
# the metrics endpoint with authn/authz. These configurations
|
||||||
|
# ensure that only authorized users and service accounts
|
||||||
|
# can access the metrics endpoint. Comment the following
|
||||||
|
# permissions if you want to disable this protection.
|
||||||
|
# More info: https://book.kubebuilder.io/reference/metrics.html
|
||||||
|
- metrics_auth_role.yaml
|
||||||
|
- metrics_auth_role_binding.yaml
|
||||||
|
- metrics_reader_role.yaml
|
||||||
|
# For each CRD, "Admin", "Editor" and "Viewer" roles are scaffolded by
|
||||||
|
# default, aiding admins in cluster management. Those roles are
|
||||||
|
# not used by the ayatori itself. You can comment the following lines
|
||||||
|
# if you do not want those helpers be installed with your Project.
|
||||||
|
- execution_opensandboxexecutionparameters_admin_role.yaml
|
||||||
|
- execution_opensandboxexecutionparameters_editor_role.yaml
|
||||||
|
- execution_opensandboxexecutionparameters_viewer_role.yaml
|
||||||
|
- execution_kubernetesexecutionparameters_admin_role.yaml
|
||||||
|
- execution_kubernetesexecutionparameters_editor_role.yaml
|
||||||
|
- execution_kubernetesexecutionparameters_viewer_role.yaml
|
||||||
|
- execution_jobclass_admin_role.yaml
|
||||||
|
- execution_jobclass_editor_role.yaml
|
||||||
|
- execution_jobclass_viewer_role.yaml
|
||||||
|
- execution_job_admin_role.yaml
|
||||||
|
- execution_job_editor_role.yaml
|
||||||
|
- execution_job_viewer_role.yaml
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# permissions to do leader election.
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: leader-election-role
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- configmaps
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- patch
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- coordination.k8s.io
|
||||||
|
resources:
|
||||||
|
- leases
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- patch
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- events
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- patch
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: leader-election-rolebinding
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: leader-election-role
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: controller-manager
|
||||||
|
namespace: system
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: metrics-auth-role
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- authentication.k8s.io
|
||||||
|
resources:
|
||||||
|
- tokenreviews
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- apiGroups:
|
||||||
|
- authorization.k8s.io
|
||||||
|
resources:
|
||||||
|
- subjectaccessreviews
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: metrics-auth-rolebinding
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: metrics-auth-role
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: controller-manager
|
||||||
|
namespace: system
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: metrics-reader
|
||||||
|
rules:
|
||||||
|
- nonResourceURLs:
|
||||||
|
- "/metrics"
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: manager-role
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["pods"]
|
||||||
|
verbs: ["get", "list", "watch"]
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: manager-rolebinding
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: manager-role
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: controller-manager
|
||||||
|
namespace: system
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: controller-manager
|
||||||
|
namespace: system
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
apiVersion: execution.ayatori.ddupan.top/v1alpha1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: job-sample
|
||||||
|
spec:
|
||||||
|
jobClassName: default
|
||||||
|
task:
|
||||||
|
image: docker.io/library/alpine:3.22
|
||||||
|
command: ["/bin/sh", "-c"]
|
||||||
|
args: ["echo hello from Ayatori"]
|
||||||
|
ttlSecondsAfterFinished: 3600
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: execution.ayatori.ddupan.top/v1alpha1
|
||||||
|
kind: JobClass
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: default
|
||||||
|
spec:
|
||||||
|
controllerName: execution.ayatori.ddupan.top/kubernetes
|
||||||
|
parametersRef:
|
||||||
|
group: execution.ayatori.ddupan.top
|
||||||
|
kind: KubernetesExecutionParameters
|
||||||
|
name: default
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
apiVersion: execution.ayatori.ddupan.top/v1alpha1
|
||||||
|
kind: KubernetesExecutionParameters
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: default
|
||||||
|
spec:
|
||||||
|
serviceAccountName: ayatori-job
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
apiVersion: execution.ayatori.ddupan.top/v1alpha1
|
||||||
|
kind: OpenSandboxExecutionParameters
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ayatori
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: microvm
|
||||||
|
spec:
|
||||||
|
endpoint: https://opensandbox-api.example.internal
|
||||||
|
apiKeySecretRef:
|
||||||
|
namespace: ayatori-system
|
||||||
|
name: opensandbox-api
|
||||||
|
key: api-key
|
||||||
|
requestMapping: AdmissionOnly
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
## Append samples of your project ##
|
||||||
|
resources:
|
||||||
|
- execution_v1alpha1_job.yaml
|
||||||
|
- execution_v1alpha1_jobclass.yaml
|
||||||
|
- execution_v1alpha1_kubernetesexecutionparameters.yaml
|
||||||
|
- execution_v1alpha1_opensandboxexecutionparameters.yaml
|
||||||
|
# +kubebuilder:scaffold:manifestskustomizesamples
|
||||||
@@ -0,0 +1,526 @@
|
|||||||
|
# Job API v1alpha1 草案
|
||||||
|
|
||||||
|
- 状态:Draft
|
||||||
|
- 日期:2026-09-17
|
||||||
|
- API group:`execution.ayatori.ddupan.top`
|
||||||
|
- Kind:`Job`
|
||||||
|
- Scope:Namespaced
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
`Job` 表达一次有限时长、有明确退出结果的机器执行。调用者描述任务载荷和资源需求,平台
|
||||||
|
选择 execution backend 并持续观察,直到任务成功、失败或取消。
|
||||||
|
|
||||||
|
首个 adapter 使用 Kubernetes `batch/v1 Job`,第二个 adapter 使用 OpenSandbox lifecycle
|
||||||
|
与 execd API。API 不暴露 PodSpec、sandbox ID 创建参数或具体 adapter 配置,但允许表达两个
|
||||||
|
真实后端共有的 OCI image、进程、环境变量和资源语义。
|
||||||
|
|
||||||
|
`Job` 是短生命周期控制对象。完成后依据 `ttlSecondsAfterFinished` 回收,长期业务状态由调用
|
||||||
|
者保存,日志由 observability 平台保存。详细保留策略见 ADR-0005。
|
||||||
|
|
||||||
|
## 非目标
|
||||||
|
|
||||||
|
v1alpha1 不提供:
|
||||||
|
|
||||||
|
- DAG、workflow 或多步骤 task;
|
||||||
|
- 定时执行与可复用 Job template;
|
||||||
|
- 并行 completions、indexed job 或 gang scheduling;
|
||||||
|
- 自动业务重试;
|
||||||
|
- 暂停后恢复;
|
||||||
|
- 交互式 shell、endpoint、snapshot 或长生命周期 sandbox;
|
||||||
|
- workspace、cache、artifact 上传协议或结构化 task outputs;
|
||||||
|
- 永久 Job history。
|
||||||
|
|
||||||
|
上述能力应由后续独立资源或经过真实需求验证的兼容字段提供,不能通过透传 PodSpec 或
|
||||||
|
OpenSandbox extensions 提前进入 API。
|
||||||
|
|
||||||
|
## 示例
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: execution.ayatori.ddupan.top/v1alpha1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
generateName: hello-
|
||||||
|
namespace: ci
|
||||||
|
spec:
|
||||||
|
jobClassName: default
|
||||||
|
task:
|
||||||
|
image: docker.io/library/alpine:3.22
|
||||||
|
imagePullSecrets: []
|
||||||
|
command: ["/bin/sh", "-c"]
|
||||||
|
args:
|
||||||
|
- echo "hello ${TARGET}"
|
||||||
|
workingDir: /workspace
|
||||||
|
env:
|
||||||
|
- name: TARGET
|
||||||
|
value: world
|
||||||
|
- name: TOKEN
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: example-token
|
||||||
|
key: token
|
||||||
|
- name: CONFIG_VALUE
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: example-config
|
||||||
|
key: value
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: "1"
|
||||||
|
memory: 512Mi
|
||||||
|
activeDeadlineSeconds: 600
|
||||||
|
ttlSecondsAfterFinished: 3600
|
||||||
|
desiredState: Running
|
||||||
|
```
|
||||||
|
|
||||||
|
## Spec
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spec:
|
||||||
|
jobClassName: string
|
||||||
|
task: TaskSpec
|
||||||
|
resources: ResourceRequirements
|
||||||
|
activeDeadlineSeconds: int64
|
||||||
|
ttlSecondsAfterFinished: int32
|
||||||
|
desiredState: Running | Cancelled
|
||||||
|
```
|
||||||
|
|
||||||
|
### `jobClassName`
|
||||||
|
|
||||||
|
可选。引用平台管理员维护的 cluster-scoped `JobClass`。调用者选择服务等级或执行
|
||||||
|
能力,而不是直接选择 adapter driver。省略时由 namespace policy 解析默认 class;不存在
|
||||||
|
默认值时 Job 保持未接受状态,不得静默选择任意后端。
|
||||||
|
|
||||||
|
一旦 Job 被接受,该字段不可变。解析出的实际 class 写入 status,以便默认策略后来变化时仍
|
||||||
|
能恢复原执行。
|
||||||
|
|
||||||
|
`JobClass` 及其强类型 parameters API 定义后端配置和调度策略。Job API 不暴露
|
||||||
|
Kubernetes namespace、OpenSandbox endpoint、API key 或 backend raw configuration。
|
||||||
|
|
||||||
|
### `task`
|
||||||
|
|
||||||
|
必填,创建后不可变。
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
task:
|
||||||
|
image: string
|
||||||
|
imagePullSecrets: []LocalObjectReference
|
||||||
|
command: []string
|
||||||
|
args: []string
|
||||||
|
workingDir: string
|
||||||
|
env: []EnvVar
|
||||||
|
```
|
||||||
|
|
||||||
|
- `image`:必填 OCI image reference。首版允许 tag;生产策略可以通过 admission 要求 digest。
|
||||||
|
- `imagePullSecrets`:可选,同 namespace 的私有 registry 凭据引用。
|
||||||
|
- `command`:可选,覆盖 image entrypoint;空值表示使用 image 默认值。
|
||||||
|
- `args`:可选,传给 entrypoint/command。
|
||||||
|
- `workingDir`:可选;为空时使用 image/backend 默认值。
|
||||||
|
- `env`:可选,名称必须唯一。
|
||||||
|
|
||||||
|
`command` 和 `args` 使用 argv 语义,不隐式经过 shell。需要 shell 展开时,调用者必须显式
|
||||||
|
指定 `/bin/sh -c` 等命令。
|
||||||
|
|
||||||
|
#### 环境变量
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: EXAMPLE
|
||||||
|
value: literal
|
||||||
|
|
||||||
|
- name: TOKEN
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: example
|
||||||
|
key: token
|
||||||
|
optional: false
|
||||||
|
```
|
||||||
|
|
||||||
|
`value` 与 `valueFrom` 必须且只能设置一个。v1alpha1 支持同 namespace 的 `SecretKeyRef` 和
|
||||||
|
`ConfigMapKeyRef`,两者具有相同的引用、optional 和等待语义。Adapter 负责以适合后端且不
|
||||||
|
写入 Job status 的方式传递值。引用对象或 key 缺失时 Job 保持未开始并通过 Condition 报告。
|
||||||
|
Secret 内容不得复制到 Event、日志或 backend reference;ConfigMap 值虽然不视为机密,也不
|
||||||
|
写入 status,避免状态膨胀和不同后端行为不一致。
|
||||||
|
|
||||||
|
Kubernetes adapter 保留原生 `SecretKeyRef`/`ConfigMapKeyRef`,由 kubelet 在执行节点解析,
|
||||||
|
controller 不读取内容。OpenSandbox create API 只接收已经解析的环境变量值,因此该 adapter
|
||||||
|
必须读取引用并把值放入 sandbox create request。JobClass 必须明确允许 Secret 的这种
|
||||||
|
传递路径,且 controller 的日志、Event 和 status 不得记录请求正文。未来需要避免把真实凭据
|
||||||
|
暴露给 sandbox 进程时,使用 OpenSandbox Credential Vault 或 Ayatori 独立 Credential 能力,
|
||||||
|
而不是改变 `SecretKeyRef` 的既有语义。
|
||||||
|
|
||||||
|
私有镜像凭据采用 Kubernetes `kubernetes.io/dockerconfigjson` Secret。Kubernetes adapter
|
||||||
|
直接传递引用;OpenSandbox adapter 选择与目标 image registry 匹配的条目,并映射到其
|
||||||
|
`image.auth` create 参数。无法解析、没有匹配 registry 或所选 OpenSandbox runtime 不支持
|
||||||
|
per-request image auth 时,Job 以明确 reason 失败,不得退回匿名拉取后隐藏真实原因。
|
||||||
|
|
||||||
|
### `resources`
|
||||||
|
|
||||||
|
可选,使用 Kubernetes `resource.Quantity` 表示数值,但不复用完整 Pod
|
||||||
|
`ResourceRequirements` 行为。
|
||||||
|
|
||||||
|
v1alpha1 支持 `cpu` 和 `memory` 的 requests/limits。Requests 表达准入与调度需求,limits
|
||||||
|
表达执行上限。JobClass 可以提供默认值和允许范围;解析后的实际资源写入 status。
|
||||||
|
|
||||||
|
Adapter 必须显式验证能否满足请求,不能无提示地忽略 limit。后端无法区分 request 与 limit
|
||||||
|
时,其映射规则属于 JobClass,并在 Job 接受前确定。对 OpenSandbox,CPU 和内存
|
||||||
|
limits 直接映射为 sandbox VM/container 的 `resourceLimits`;requests 用于 Ayatori 的准入与
|
||||||
|
调度,并可由 JobClass 映射到后端 resource request 或 capacity profile。映射失败必须
|
||||||
|
显式拒绝或失败,不能静默降低资源保证。
|
||||||
|
|
||||||
|
### `activeDeadlineSeconds`
|
||||||
|
|
||||||
|
可选,必须大于零。表示从实际执行开始到任务必须终止的最长时间,不包含排队、class 解析或
|
||||||
|
后端 provisioning 时间。到期后 controller 请求终止后端,最终以 `Succeeded=False`、
|
||||||
|
`reason=DeadlineExceeded` 结束。
|
||||||
|
|
||||||
|
后端自身的 timeout 可以作为执行机制,但 Ayatori controller 仍以 `status.startTime` 和观察
|
||||||
|
结果维护领域语义。调度等待超时是不同概念,v1alpha1 不提供。
|
||||||
|
|
||||||
|
### `ttlSecondsAfterFinished`
|
||||||
|
|
||||||
|
可选,必须大于或等于零。语义与 Kubernetes Job 一致:从终态 transition time 起计算,零
|
||||||
|
表示立即具备删除资格。该字段在任务完成前后均可修改,但不能保证在既有 TTL 已过期后通过
|
||||||
|
延长 TTL 阻止并发删除。
|
||||||
|
|
||||||
|
平台应通过 schema、CEL 或 policy 设置最大值和推荐默认值。controller 本身不偷偷填充一个
|
||||||
|
无法从 spec 观察到的永久策略。
|
||||||
|
|
||||||
|
### `desiredState`
|
||||||
|
|
||||||
|
可选,默认 `Running`。允许的状态迁移只有:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Running → Cancelled
|
||||||
|
```
|
||||||
|
|
||||||
|
设置 `Cancelled` 表示请求终止当前执行并保留 Job 至 TTL 到期。取消是尽力而为的异步操作;
|
||||||
|
只有 adapter 确认执行不会继续后,Job 才进入终态。字段不得从 `Cancelled` 改回 `Running`。
|
||||||
|
重新执行必须创建新的 Job。
|
||||||
|
|
||||||
|
v1alpha1 不提供 suspend/resume。对任意后端可靠实现 checkpoint/resume 并非共同能力,且暂停
|
||||||
|
不应被伪装为取消。
|
||||||
|
|
||||||
|
## 不可变性
|
||||||
|
|
||||||
|
创建后仅允许修改:
|
||||||
|
|
||||||
|
- `spec.desiredState`,且只能单向变为 `Cancelled`;
|
||||||
|
- `spec.ttlSecondsAfterFinished`。
|
||||||
|
|
||||||
|
`task`、`resources`、`activeDeadlineSeconds` 和 `jobClassName` 均不可变。首选 CRD CEL
|
||||||
|
validation 表达这些约束;只有 schema/CEL 无法正确表达时才引入 admission webhook。
|
||||||
|
|
||||||
|
Controller reconcile 的技术重试不表示任务重跑。v1alpha1 每个 Job 最多启动一个逻辑执行;
|
||||||
|
adapter 必须使用 Job UID 作为幂等键。若请求结果未知,controller 必须先 Observe,不能因为
|
||||||
|
网络超时重新创建可能已经开始的执行。
|
||||||
|
|
||||||
|
首个 Kubernetes adapter 创建 `backoffLimit: 0`、`restartPolicy: Never` 的原生 Job,避免继承
|
||||||
|
Kubernetes 默认的多次业务执行语义。需要重新执行时创建新的 Ayatori Job。
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
status:
|
||||||
|
observedGeneration: 1
|
||||||
|
conditions:
|
||||||
|
- type: Accepted
|
||||||
|
status: "True"
|
||||||
|
reason: Valid
|
||||||
|
observedGeneration: 1
|
||||||
|
lastTransitionTime: ...
|
||||||
|
- type: Scheduled
|
||||||
|
status: "True"
|
||||||
|
reason: BackendCreated
|
||||||
|
observedGeneration: 1
|
||||||
|
lastTransitionTime: ...
|
||||||
|
- type: Succeeded
|
||||||
|
status: "Unknown"
|
||||||
|
reason: Running
|
||||||
|
observedGeneration: 1
|
||||||
|
lastTransitionTime: ...
|
||||||
|
resolvedJobClass:
|
||||||
|
name: default
|
||||||
|
uid: 8aa4...
|
||||||
|
controllerName: execution.ayatori.ddupan.top/kubernetes
|
||||||
|
parametersRef:
|
||||||
|
group: execution.ayatori.ddupan.top
|
||||||
|
kind: KubernetesExecutionParameters
|
||||||
|
name: default
|
||||||
|
uid: c413...
|
||||||
|
effectiveResources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: "1"
|
||||||
|
memory: 512Mi
|
||||||
|
execution:
|
||||||
|
adapter: kubernetes
|
||||||
|
references:
|
||||||
|
- type: Job
|
||||||
|
id: 5cb0...
|
||||||
|
startTime: ...
|
||||||
|
completionTime: ...
|
||||||
|
result:
|
||||||
|
exitCode: 0
|
||||||
|
reason: Completed
|
||||||
|
```
|
||||||
|
|
||||||
|
### Conditions
|
||||||
|
|
||||||
|
使用标准 `metav1.Condition`。v1alpha1 定义三个核心 Condition:
|
||||||
|
|
||||||
|
- `Accepted`:spec、引用、policy 和 JobClass 已解析,可进入调度;
|
||||||
|
- `Scheduled`:后端已确定并存在可观察的逻辑执行;
|
||||||
|
- `Succeeded`:任务结果。`Unknown` 表示尚未结束,`True` 表示成功,`False` 表示已经失败或
|
||||||
|
取消。
|
||||||
|
|
||||||
|
失败不使用单独的 `Failed` Condition。`Succeeded=True` 与 `Failed=True` 会形成需要额外维护的
|
||||||
|
互斥状态,而标准三态 Condition 已能完整表达一次执行:运行中为 `Unknown`、成功为 `True`、
|
||||||
|
失败为 `False`。失败类型由稳定 reason 区分;这与 Tekton `TaskRun` 的状态约定一致。
|
||||||
|
|
||||||
|
不增加与 Conditions 重复的 `phase` 字段。面向 CLI 的阶段摘要由 printer columns 或客户端从
|
||||||
|
Conditions 推导,避免两个状态源发生漂移。
|
||||||
|
|
||||||
|
常用 `Succeeded` reason 初始包括:
|
||||||
|
|
||||||
|
- `Pending`、`Scheduling`、`Running`;
|
||||||
|
- `Completed`;
|
||||||
|
- `ProcessFailed`;
|
||||||
|
- `DeadlineExceeded`;
|
||||||
|
- `Cancelled`;
|
||||||
|
- `BackendLost`;
|
||||||
|
- `ResultUnknown`。
|
||||||
|
|
||||||
|
Reason 是稳定、机器可读的 PascalCase 标识;message 面向人类且不得承载程序逻辑。
|
||||||
|
|
||||||
|
## 状态机
|
||||||
|
|
||||||
|
状态机名称用于设计、测试和 metrics,不增加持久化 `status.phase`。当前状态必须能够从 spec、
|
||||||
|
deletionTimestamp、Conditions、时间和 execution references 唯一推导。
|
||||||
|
|
||||||
|
### 状态定义
|
||||||
|
|
||||||
|
| 状态 | 判定摘要 | 含义 |
|
||||||
|
|---|---|---|
|
||||||
|
| `Resolving` | `Accepted!=True`,非终态 | 等待 JobClass、Secret、ConfigMap 或 policy 解析 |
|
||||||
|
| `Scheduling` | `Accepted=True`、`Scheduled!=True` | 选择 adapter 并幂等创建后端执行 |
|
||||||
|
| `Starting` | `Scheduled=True`、无 `startTime` | 后端已存在,任务主体尚未确认开始 |
|
||||||
|
| `Running` | 有 `startTime`、`Succeeded=Unknown` | 任务主体正在执行 |
|
||||||
|
| `Cancelling` | `desiredState=Cancelled`、非终态 | 正在确认后端已经停止 |
|
||||||
|
| `ResultUnknown` | `Succeeded=Unknown/ResultUnknown` | 无法证明任务仍在运行或已经停止 |
|
||||||
|
| `Succeeded` | `Succeeded=True` | 成功终态 |
|
||||||
|
| `Failed` | `Succeeded=False`,reason 非 `Cancelled` | 失败终态 |
|
||||||
|
| `Cancelled` | `Succeeded=False/Cancelled` | 取消终态 |
|
||||||
|
| `Deleting` | 存在 `deletionTimestamp` | finalizer 正在停止并清理后端,覆盖其他状态 |
|
||||||
|
|
||||||
|
存在多个判定条件时按 `Deleting → terminal → Cancelling → ResultUnknown → Running → Starting →
|
||||||
|
Scheduling → Resolving` 的优先级推导,保证状态唯一。
|
||||||
|
|
||||||
|
`ResultUnknown` 不是终态,不设置 `completionTime`,也不启动 TTL。只有确认任务已经停止,才能
|
||||||
|
转为成功、失败或取消。暂时无法联系后端不等于后端执行失败。
|
||||||
|
|
||||||
|
### 正常转移
|
||||||
|
|
||||||
|
```text
|
||||||
|
Resolving
|
||||||
|
│ 引用与策略解析完成
|
||||||
|
▼
|
||||||
|
Scheduling
|
||||||
|
│ 后端逻辑执行已建立并持久化引用
|
||||||
|
▼
|
||||||
|
Starting
|
||||||
|
│ adapter 确认任务主体开始
|
||||||
|
▼
|
||||||
|
Running ───────────────→ Succeeded
|
||||||
|
└──────────────────→ Failed
|
||||||
|
```
|
||||||
|
|
||||||
|
后端在任务主体开始前就确定失败,例如 image pull、runtime 不兼容或 provisioning 失败,可以从
|
||||||
|
`Scheduling` 或 `Starting` 直接进入 `Failed`,此时 `startTime` 允许为空。
|
||||||
|
|
||||||
|
### 取消转移
|
||||||
|
|
||||||
|
```text
|
||||||
|
Resolving ─┐
|
||||||
|
Scheduling ─┤
|
||||||
|
Starting ─┼→ Cancelling → Cancelled
|
||||||
|
Running ─┤
|
||||||
|
ResultUnknown ─┘
|
||||||
|
```
|
||||||
|
|
||||||
|
尚未创建后端执行时,取消可以立即确认。已经存在或可能存在后端执行时,必须反复执行
|
||||||
|
Cancel/Observe,确认不会继续运行后才能进入 `Cancelled`。取消请求与成功完成并发时,以先从
|
||||||
|
后端确认到的不可逆事实为准:已经成功完成的任务保持 `Succeeded`,不能改写成 `Cancelled`。
|
||||||
|
|
||||||
|
### 不确定结果与恢复
|
||||||
|
|
||||||
|
```text
|
||||||
|
Ensure/Observe 返回歧义
|
||||||
|
↓
|
||||||
|
ResultUnknown
|
||||||
|
├── 找回执行 → Starting / Running
|
||||||
|
├── 找到终态 → Succeeded / Failed / Cancelled
|
||||||
|
└── 管理员确认无法继续 → Failed(BackendLost)
|
||||||
|
```
|
||||||
|
|
||||||
|
在 `Ensure` 请求超时且尚未成功写入 external reference 时,adapter 必须使用 Job UID 查询后端,
|
||||||
|
不能直接再次创建。Controller 重启后遵循相同规则。
|
||||||
|
|
||||||
|
### 删除与 TTL 转移
|
||||||
|
|
||||||
|
任意状态收到 deletionTimestamp 后进入 `Deleting`。若执行可能存在,先 Cancel/Observe,再
|
||||||
|
Delete 后端资源并移除 finalizer。TTL controller 只对具有 `completionTime` 的三个终态发起
|
||||||
|
删除;`Resolving`、`Scheduling`、`Starting`、`Running`、`Cancelling` 和 `ResultUnknown` 均不
|
||||||
|
具备 TTL 删除资格。
|
||||||
|
|
||||||
|
### 状态不变量
|
||||||
|
|
||||||
|
- `Succeeded=True/False` 是不可逆终态;终态 reason、`completionTime` 和 result 不再改变。
|
||||||
|
- `startTime` 和 `completionTime` 一旦设置不可改变;两者都存在时 completionTime 不早于
|
||||||
|
startTime。
|
||||||
|
- `Succeeded=True` 必须具有 `completionTime`,可以没有 exit code,但 adapter 应说明原因。
|
||||||
|
- `Succeeded=False` 必须具有 `completionTime`;进程失败且能取得退出码时必须保存 exit code。
|
||||||
|
- 非终态的 `Succeeded` 必须为 `Unknown`,不得省略为具有歧义的空状态。
|
||||||
|
- `Scheduled=True` 前不得设置 `startTime`;一旦为 True 不再回退。
|
||||||
|
- execution references 只能由 controller 写入;已有引用不能静默替换成新的逻辑执行。
|
||||||
|
- Job UID 是执行幂等键;同名但不同 UID 的 Job 必须被视为不同执行。
|
||||||
|
- `desiredState=Cancelled` 后不得创建新的后端执行。
|
||||||
|
- reconcile 错误和退避不得修改任务的业务结果。
|
||||||
|
|
||||||
|
### 状态机测试矩阵
|
||||||
|
|
||||||
|
实现必须至少覆盖以下 table-driven unit tests,并为关键恢复路径提供 envtest:
|
||||||
|
|
||||||
|
| 类别 | 场景 | 必要断言 |
|
||||||
|
|---|---|---|
|
||||||
|
| 正常 | 创建、开始、退出 0 | 单次 Ensure,时间与成功终态正确 |
|
||||||
|
| 正常 | 主进程非零退出 | `Succeeded=False/ProcessFailed` 与 exit code |
|
||||||
|
| 解析 | JobClass 后创建 | 不提前 Ensure,引用出现后继续 |
|
||||||
|
| 解析 | Secret/ConfigMap 或 key 后创建 | 不泄露值,解析后只启动一次 |
|
||||||
|
| 后端 | image pull 或 provisioning 失败 | 未设置 startTime 的失败终态合法 |
|
||||||
|
| 幂等 | Ensure 成功但 status 写入前崩溃 | 通过 UID 找回,不能创建第二次执行 |
|
||||||
|
| 幂等 | 重复 reconcile 与重复事件 | 不产生额外执行,不改变终态时间 |
|
||||||
|
| 恢复 | controller 在各非终态重启 | 从持久 status/reference 恢复正确状态 |
|
||||||
|
| 未知 | Ensure/Observe 超时且结果不明 | 保持非终态,不设 completionTime,不触发 TTL |
|
||||||
|
| 未知 | 后端恢复后找回运行任务 | 从 ResultUnknown 返回 Running |
|
||||||
|
| 未知 | 管理员确认执行丢失 | 只在确认后进入 `Failed/BackendLost` |
|
||||||
|
| 取消 | 在解析、调度、启动、运行阶段取消 | 不再创建或确认停止后才进入 Cancelled |
|
||||||
|
| 竞态 | 取消与成功完成并发 | 已确认成功不被取消覆盖 |
|
||||||
|
| 超时 | active deadline 到期 | 请求取消,确认停止后 `DeadlineExceeded` |
|
||||||
|
| 删除 | 每个非终态阶段删除 | finalizer 清理完成前对象不消失 |
|
||||||
|
| 删除 | 后端暂时不可达 | finalizer 保留并重试,不误报已清理 |
|
||||||
|
| TTL | 三种终态到期 | 到期前不删,到期后带 UID precondition 删除 |
|
||||||
|
| TTL | controller 在等待 TTL 时重启 | informer 恢复计时,最终删除一次 |
|
||||||
|
| TTL | 到期附近延长 TTL | 最终 GET 重新核对最新 TTL |
|
||||||
|
| 隔离 | 同名 Job 删除并以新 UID 重建 | 旧队列项和旧后端不得影响新 Job |
|
||||||
|
| 校验 | 修改不可变字段或取消后恢复 Running | schema/CEL 拒绝请求 |
|
||||||
|
| 引用 | OpenSandbox 保存 Sandbox 与 Command 引用 | 顺序重试后引用稳定且无凭据 |
|
||||||
|
|
||||||
|
### 时间
|
||||||
|
|
||||||
|
- `startTime`:adapter 确认任务主体开始执行的时间,而不是 CR 创建或 backend provisioning
|
||||||
|
时间;设置后不可改变。
|
||||||
|
- `completionTime`:进入最终成功、失败或取消状态的时间;设置后不可改变。
|
||||||
|
|
||||||
|
TTL 以 `completionTime` 为基准。若后端已经完成但结果暂时无法确认,不得猜测 completionTime。
|
||||||
|
|
||||||
|
### Execution reference
|
||||||
|
|
||||||
|
`status.execution` 是 execution 领域定义的正式 API 字段,保存 controller 重启后重新 Observe
|
||||||
|
所需的最小稳定引用:
|
||||||
|
|
||||||
|
- `adapter`:实际 adapter 类型;
|
||||||
|
- `references`:一个或多个由 adapter 定义的不透明外部引用。
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
execution:
|
||||||
|
adapter: opensandbox
|
||||||
|
references:
|
||||||
|
- type: Sandbox
|
||||||
|
id: sandbox-123
|
||||||
|
- type: Command
|
||||||
|
id: command-456
|
||||||
|
```
|
||||||
|
|
||||||
|
单个 `externalID` 不足以表达 OpenSandbox 的 sandbox 与 command 两级资源。`type` 和 `id` 的
|
||||||
|
值由对应 adapter 定义,调用者只能用于诊断和关联,不能据此实现领域逻辑。execution 领域将
|
||||||
|
每个引用限制为 `type` 与 `id` 两个非空、有长度上限的字符串,不提供任意 metadata map 或
|
||||||
|
raw JSON。引用不包含 endpoint、凭据或 Secret 内容。Kubernetes adapter 可以另外通过 owner
|
||||||
|
reference 管理原生 Job,但仍需把恢复所需引用持久化,并保证同名重建安全。
|
||||||
|
|
||||||
|
该结构不提升为跨领域共享的万能 ExternalReference。VM、数据库和 LB 等领域根据真实后端
|
||||||
|
需要定义自己的受限引用 schema,只有多个领域出现语义完全一致的实际重复后才考虑共享。
|
||||||
|
|
||||||
|
### Result
|
||||||
|
|
||||||
|
`result.exitCode` 只在后端能够确定主进程退出码时设置。调度失败、取消、后端丢失等情况可以
|
||||||
|
没有退出码。`result.reason` 提供简短分类;详细诊断写入 Condition message 和 observability,
|
||||||
|
不得把完整日志写入 status。
|
||||||
|
|
||||||
|
Job UID 是跨后端日志、metrics 和 traces 的主要 correlation identity。Adapter 必须将
|
||||||
|
namespace、name 和 UID 传入执行环境或后端 metadata;高基数字段如何索引由 observability
|
||||||
|
平台决定,API 不要求把 UID 配置为日志 label。
|
||||||
|
|
||||||
|
## 删除与 finalizer
|
||||||
|
|
||||||
|
Execution controller 在可能创建外部执行前添加
|
||||||
|
`execution.ayatori.ddupan.top/job-cleanup` finalizer。
|
||||||
|
|
||||||
|
删除一个活动 Job 表示取消并清理,而不是 orphan:
|
||||||
|
|
||||||
|
1. 请求 adapter 终止执行;
|
||||||
|
2. Observe,确认执行不会继续;
|
||||||
|
3. 删除后端临时资源与短期凭据;
|
||||||
|
4. 移除 finalizer。
|
||||||
|
|
||||||
|
首版不提供用户可选 orphan policy。让一次性任务脱离控制面继续运行既难以观察,也可能产生
|
||||||
|
副作用。后端长期不可达时由管理员根据 runbook 判断并强制移除 finalizer,该操作必须可审计。
|
||||||
|
|
||||||
|
TTL controller 只发起 Job 删除,所有手工删除和 TTL 删除都经过相同 finalizer 路径。
|
||||||
|
|
||||||
|
## Adapter contract 对 API 的保证
|
||||||
|
|
||||||
|
每个 execution adapter 必须提供以下语义,而非暴露自身 SDK 类型:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Ensure 幂等地建立以 Job UID 标识的一个逻辑执行
|
||||||
|
Observe 返回尚未开始、运行、成功、失败、取消或结果未知
|
||||||
|
Cancel 请求停止且可被重复调用
|
||||||
|
Delete 清理后端临时资源且可被重复调用
|
||||||
|
```
|
||||||
|
|
||||||
|
`Ensure` 的网络超时不能直接触发第二次执行。Adapter 必须能够通过 UID/metadata 查找已创建的
|
||||||
|
后端对象,或返回 `ResultUnknown` 交由人工处理。
|
||||||
|
|
||||||
|
Kubernetes adapter 与 OpenSandbox adapter 实现后,应复审 contract 和 API。只有两个真实
|
||||||
|
实现都需要且语义相同的字段才提升为通用能力;后端特有功能优先进入 JobClass 或独立
|
||||||
|
资源,不增加 `rawConfig`。
|
||||||
|
|
||||||
|
OpenSandbox 支持从 OCI image 创建 sandbox,但不保证每个 image 都能在所选 runtime、架构或
|
||||||
|
安全 profile 下成功启动。Adapter 对已知不支持的组合应尽早报告;image pull、进程启动或
|
||||||
|
运行时不兼容等实际后端失败最终统一表现为 `Succeeded=False`,并以 reason/message 保留可
|
||||||
|
诊断原因。这不要求 Ayatori 在提交前证明任意 OCI image 一定可运行。
|
||||||
|
|
||||||
|
## 待后续设计
|
||||||
|
|
||||||
|
- capability-based class 自动选择;
|
||||||
|
- 私有 image registry 的凭据和统一 workload identity;
|
||||||
|
- artifact、workspace 与 cache 的独立 API;
|
||||||
|
- Job 创建速率、并发、quota、公平调度以及是否集成 Kueue;
|
||||||
|
- observability correlation 的具体 OpenTelemetry/Loki 字段约定;
|
||||||
|
- 调用者错过 TTL 时是否需要可选的最小审计记录。
|
||||||
|
|
||||||
|
这些问题不阻塞首个 Kubernetes adapter 的 API review;image pull 的最小凭据路径必须在实现
|
||||||
|
前通过 Kubernetes 与 OpenSandbox adapter 测试验证。
|
||||||
|
|
||||||
|
## 成熟实现参考
|
||||||
|
|
||||||
|
- [Kubernetes Job](https://kubernetes.io/docs/concepts/workloads/controllers/job/)
|
||||||
|
- [Kubernetes Job API](https://kubernetes.io/docs/reference/kubernetes-api/batch/job-v1/)
|
||||||
|
- [Tekton Pipeline API](https://tekton.dev/docs/pipelines/pipeline-api/)
|
||||||
|
- [Kueue Workload](https://kueue.sigs.k8s.io/docs/concepts/workload/)
|
||||||
|
- [OpenSandbox API specifications](https://github.com/opensandbox-group/OpenSandbox/blob/main/docs/api/index.md)
|
||||||
@@ -0,0 +1,411 @@
|
|||||||
|
# JobClass API v1alpha1 草案
|
||||||
|
|
||||||
|
- 状态:Draft
|
||||||
|
- 日期:2026-09-17
|
||||||
|
- API group:`execution.ayatori.ddupan.top`
|
||||||
|
- Kind:`JobClass`
|
||||||
|
- Scope:Cluster
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
`JobClass` 是平台管理员提供给 Job 调用者的执行服务等级。名称表达稳定的用户语义,
|
||||||
|
例如 `default`、`rootless`、`microvm` 或 `trusted-infra`;调用者不需要知道它当前由 Kubernetes
|
||||||
|
还是 OpenSandbox 实现。
|
||||||
|
|
||||||
|
JobClass 负责:
|
||||||
|
|
||||||
|
- 选择拥有该 class 的 adapter/controller;
|
||||||
|
- 引用 adapter 自己的强类型参数对象;
|
||||||
|
- 限制允许使用该 class 的 namespace;
|
||||||
|
- 提供跨后端一致的资源默认值与范围;
|
||||||
|
- 向 Job controller 报告配置是否被接受、后端是否可用。
|
||||||
|
|
||||||
|
它不负责保存队列状态、并发配额、Job history 或任意后端 raw config。
|
||||||
|
|
||||||
|
## 设计依据
|
||||||
|
|
||||||
|
- Kubernetes `RuntimeClass` 使用 cluster-scoped class 将调用者与具体 runtime handler、调度约束
|
||||||
|
和 overhead 隔离。
|
||||||
|
- `StorageClass` 允许管理员用稳定名称提供不同服务等级,并由调用者显式或默认选择。
|
||||||
|
- Gateway API `GatewayClass` 使用 `controllerName + parametersRef` 将稳定 class API 与实现专用
|
||||||
|
参数分离,并通过 `Accepted` Condition 报告配置有效性。
|
||||||
|
- Kueue `ResourceFlavor` 将资源规格与具体节点标签、taint 等 placement 细节分开。
|
||||||
|
|
||||||
|
Ayatori 采用 GatewayClass 风格的参数引用,不在 JobClass 中建立随 adapter 数量膨胀的
|
||||||
|
union,也不使用 `map[string]any`。
|
||||||
|
|
||||||
|
## 示例
|
||||||
|
|
||||||
|
### Kubernetes execution
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: execution.ayatori.ddupan.top/v1alpha1
|
||||||
|
kind: JobClass
|
||||||
|
metadata:
|
||||||
|
name: rootless
|
||||||
|
annotations:
|
||||||
|
execution.ayatori.ddupan.top/is-default-job-class: "true"
|
||||||
|
spec:
|
||||||
|
controllerName: execution.ayatori.ddupan.top/kubernetes
|
||||||
|
parametersRef:
|
||||||
|
group: execution.ayatori.ddupan.top
|
||||||
|
kind: KubernetesExecutionParameters
|
||||||
|
name: rootless
|
||||||
|
allowedNamespaces:
|
||||||
|
matchLabels:
|
||||||
|
ayatori.ddupan.top/execution: enabled
|
||||||
|
resources:
|
||||||
|
defaults:
|
||||||
|
requests:
|
||||||
|
cpu: 250m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: "2"
|
||||||
|
memory: 2Gi
|
||||||
|
maximum:
|
||||||
|
limits:
|
||||||
|
cpu: "8"
|
||||||
|
memory: 16Gi
|
||||||
|
---
|
||||||
|
apiVersion: execution.ayatori.ddupan.top/v1alpha1
|
||||||
|
kind: KubernetesExecutionParameters
|
||||||
|
metadata:
|
||||||
|
name: rootless
|
||||||
|
spec:
|
||||||
|
serviceAccountName: ayatori-job
|
||||||
|
runtimeClassName: runc
|
||||||
|
scheduling:
|
||||||
|
nodeSelector:
|
||||||
|
ayatori.ddupan.top/node-role: execution
|
||||||
|
tolerations:
|
||||||
|
- key: ayatori.ddupan.top/execution
|
||||||
|
operator: Equal
|
||||||
|
value: "true"
|
||||||
|
effect: NoSchedule
|
||||||
|
podSecurityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
```
|
||||||
|
|
||||||
|
### OpenSandbox execution
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: execution.ayatori.ddupan.top/v1alpha1
|
||||||
|
kind: JobClass
|
||||||
|
metadata:
|
||||||
|
name: microvm
|
||||||
|
spec:
|
||||||
|
controllerName: execution.ayatori.ddupan.top/opensandbox
|
||||||
|
parametersRef:
|
||||||
|
group: execution.ayatori.ddupan.top
|
||||||
|
kind: OpenSandboxExecutionParameters
|
||||||
|
name: microvm
|
||||||
|
allowedNamespaces:
|
||||||
|
matchLabels:
|
||||||
|
ayatori.ddupan.top/microvm-access: "true"
|
||||||
|
resources:
|
||||||
|
defaults:
|
||||||
|
requests:
|
||||||
|
cpu: "1"
|
||||||
|
memory: 1Gi
|
||||||
|
limits:
|
||||||
|
cpu: "2"
|
||||||
|
memory: 2Gi
|
||||||
|
maximum:
|
||||||
|
limits:
|
||||||
|
cpu: "8"
|
||||||
|
memory: 16Gi
|
||||||
|
---
|
||||||
|
apiVersion: execution.ayatori.ddupan.top/v1alpha1
|
||||||
|
kind: OpenSandboxExecutionParameters
|
||||||
|
metadata:
|
||||||
|
name: microvm
|
||||||
|
spec:
|
||||||
|
endpoint: https://opensandbox-api.example.internal
|
||||||
|
apiKeySecretRef:
|
||||||
|
namespace: ayatori-system
|
||||||
|
name: opensandbox-api
|
||||||
|
key: api-key
|
||||||
|
poolRef: microvm
|
||||||
|
requestMapping: AdmissionOnly
|
||||||
|
allowSecretEnv: true
|
||||||
|
allowImageAuth: true
|
||||||
|
allowInsecureHTTP: false
|
||||||
|
```
|
||||||
|
|
||||||
|
`endpoint` 默认必须使用 HTTPS。隔离的本地开发环境可以显式设置
|
||||||
|
`allowInsecureHTTP: true` 使用 HTTP;生产配置不得启用该开关。
|
||||||
|
|
||||||
|
## JobClass spec
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spec:
|
||||||
|
controllerName: string
|
||||||
|
parametersRef: ParametersReference
|
||||||
|
allowedNamespaces: LabelSelector
|
||||||
|
resources: ExecutionResourcePolicy
|
||||||
|
```
|
||||||
|
|
||||||
|
### `controllerName`
|
||||||
|
|
||||||
|
必填、创建后不可变。使用 domain-prefixed path 标识负责处理该 class 和 Job 的 controller,例如:
|
||||||
|
|
||||||
|
```text
|
||||||
|
execution.ayatori.ddupan.top/kubernetes
|
||||||
|
execution.ayatori.ddupan.top/opensandbox
|
||||||
|
```
|
||||||
|
|
||||||
|
它是 controller 所有权标识,不是任意可执行插件名称。一个 controller 只能处理自己明确支持
|
||||||
|
的名称。未来 adapter 拆成独立 Deployment 时,class 和 Job API 无需改变。
|
||||||
|
|
||||||
|
### `parametersRef`
|
||||||
|
|
||||||
|
必填、创建后不可变:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
parametersRef:
|
||||||
|
group: execution.ayatori.ddupan.top
|
||||||
|
kind: KubernetesExecutionParameters
|
||||||
|
name: rootless
|
||||||
|
```
|
||||||
|
|
||||||
|
v1alpha1 只允许引用 cluster-scoped 参数对象,并限制 `group`、`kind`、`name` 的长度与格式。
|
||||||
|
每个 controller 明确列出支持的 kind;引用 ConfigMap、Secret 或未知 CRD 不被接受。
|
||||||
|
|
||||||
|
参数引用只有一层,参数 CRD 可以进一步引用 Secret 等运行配置。禁止嵌套通用参数链和 raw
|
||||||
|
JSON,避免 class 成为无法校验的配置转发器。
|
||||||
|
|
||||||
|
### `allowedNamespaces`
|
||||||
|
|
||||||
|
可选 Kubernetes `LabelSelector`。Job 所在 namespace 必须匹配才可使用该 class。省略表示允许
|
||||||
|
所有 namespace;这是显式的管理员选择,而不是用户能力。
|
||||||
|
|
||||||
|
该检查由 Job controller 执行,并应尽可能增加 CEL/admission policy 作为快速反馈。用户即使
|
||||||
|
知道 privileged class 名称,也不能仅靠设置 `jobClassName` 绕过授权。
|
||||||
|
|
||||||
|
Namespace label 在 Job 被接受后发生变化,不中断已经运行的 Job,但影响新的 Job。紧急终止
|
||||||
|
使用独立管理员操作,不通过修改 selector 隐式杀死任务。
|
||||||
|
|
||||||
|
### `resources`
|
||||||
|
|
||||||
|
可选,定义后端无关的 CPU、内存策略:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
resources:
|
||||||
|
defaults:
|
||||||
|
requests: {cpu, memory}
|
||||||
|
limits: {cpu, memory}
|
||||||
|
minimum:
|
||||||
|
requests: {cpu, memory}
|
||||||
|
limits: {cpu, memory}
|
||||||
|
maximum:
|
||||||
|
requests: {cpu, memory}
|
||||||
|
limits: {cpu, memory}
|
||||||
|
```
|
||||||
|
|
||||||
|
规则为:
|
||||||
|
|
||||||
|
1. Job 未设置的值由 defaults 补齐;
|
||||||
|
2. 解析结果必须满足 minimum/maximum;
|
||||||
|
3. CPU 与内存 request 不得大于对应 limit;
|
||||||
|
4. 解析后的 effective resources 写入 Job status;
|
||||||
|
5. 后续修改 class 不改变已经接受的 Job;
|
||||||
|
6. adapter 不能静默降低 effective resources。
|
||||||
|
|
||||||
|
只支持 CPU 和内存。GPU、临时磁盘等资源在出现真实后端需求后增加,不先复制完整 Kubernetes
|
||||||
|
ResourceList。
|
||||||
|
|
||||||
|
Runtime/VM overhead 是 adapter 参数或后端调度实现,不计入用户请求的 task resources。
|
||||||
|
Kubernetes adapter 应优先利用 RuntimeClass Pod overhead;OpenSandbox adapter 在其 capacity
|
||||||
|
profile 中计算 microVM overhead。
|
||||||
|
|
||||||
|
## 默认 class 选择
|
||||||
|
|
||||||
|
Job 显式设置 `spec.jobClassName` 时始终优先使用该值。省略时按以下顺序解析:
|
||||||
|
|
||||||
|
1. Job namespace annotation
|
||||||
|
`execution.ayatori.ddupan.top/default-job-class`;
|
||||||
|
2. 唯一带有
|
||||||
|
`execution.ayatori.ddupan.top/is-default-job-class: "true"` annotation 的 JobClass。
|
||||||
|
|
||||||
|
若不存在默认 class,Job 保持 `Accepted=False/NoDefaultJobClass`。若存在多个全局默认值,
|
||||||
|
Job 保持 `Accepted=False/AmbiguousDefaultJobClass`,同时产生平台告警;不得模仿
|
||||||
|
StorageClass 选择最新创建对象,因为执行隔离与权限不应随创建时间变化。
|
||||||
|
|
||||||
|
解析后 Job status 保存:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
resolvedJobClass:
|
||||||
|
name: rootless
|
||||||
|
uid: 8aa4...
|
||||||
|
controllerName: execution.ayatori.ddupan.top/kubernetes
|
||||||
|
parametersRef:
|
||||||
|
group: execution.ayatori.ddupan.top
|
||||||
|
kind: KubernetesExecutionParameters
|
||||||
|
name: rootless
|
||||||
|
uid: c413...
|
||||||
|
```
|
||||||
|
|
||||||
|
Job 后续 reconcile 使用已解析引用,不能因 namespace 默认值或全局默认 class 改变而切换
|
||||||
|
adapter。若同名 class 被删除并重建,UID 不匹配,现存 Job 不得自动采用新对象。
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
status:
|
||||||
|
observedGeneration: 1
|
||||||
|
conditions:
|
||||||
|
- type: Accepted
|
||||||
|
status: "True"
|
||||||
|
reason: Accepted
|
||||||
|
observedGeneration: 1
|
||||||
|
lastTransitionTime: ...
|
||||||
|
- type: Ready
|
||||||
|
status: "True"
|
||||||
|
reason: BackendReachable
|
||||||
|
observedGeneration: 1
|
||||||
|
lastTransitionTime: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### `Accepted`
|
||||||
|
|
||||||
|
表示 controller 已识别 controllerName,parametersRef 指向受支持且 schema 有效的对象,通用
|
||||||
|
resource policy 自洽。无效 class 使用 `Accepted=False` 和稳定 reason,例如:
|
||||||
|
|
||||||
|
- `UnsupportedController`;
|
||||||
|
- `InvalidParametersReference`;
|
||||||
|
- `ParametersNotFound`;
|
||||||
|
- `InvalidResourcePolicy`。
|
||||||
|
|
||||||
|
### `Ready`
|
||||||
|
|
||||||
|
表示该 class 当前具备接受新执行的基本条件。Kubernetes adapter 检查 RuntimeClass 等集群级
|
||||||
|
依赖;具体 namespace 中的 ServiceAccount 在 Job 调度时检查。OpenSandbox adapter 检查参数
|
||||||
|
引用、认证材料和后端健康端点。
|
||||||
|
|
||||||
|
`Ready=False` 阻止创建新的后端执行,但不改变已经开始 Job 的终态。Controller 仍必须尝试
|
||||||
|
Observe、Cancel 和 Delete 已存在执行,不能因 class 不 Ready 而停止清理。
|
||||||
|
|
||||||
|
Ready 是观测值,不是容量预留。容量不足、排队和并发配额属于调度系统,不通过 Ready 频繁
|
||||||
|
抖动。
|
||||||
|
|
||||||
|
## 生命周期与修改
|
||||||
|
|
||||||
|
- `controllerName` 和 `parametersRef` 不可变;切换后端必须创建新 class 名称。
|
||||||
|
- `allowedNamespaces` 与 resource policy 可以修改,只影响尚未接受的新 Job。
|
||||||
|
- adapter 参数对象允许更新 endpoint、Secret 引用和其他运维配置,以支持凭据轮换与故障切换。
|
||||||
|
- 参数更新不得使 adapter 为现存 Job 创建新的逻辑执行;Job 中已持久化的 execution reference
|
||||||
|
始终优先。
|
||||||
|
|
||||||
|
JobClass controller 添加保护 finalizer。删除 class 前必须确认不存在引用其 UID 的非终态
|
||||||
|
Job。终态 Job 已完成后端清理,不阻塞 class 删除;其 TTL 回收不再需要 class 后端配置。
|
||||||
|
|
||||||
|
参数对象删除保护由各 adapter controller 负责。在仍有 class 引用时,参数对象不得被无提示
|
||||||
|
删除。强制移除 finalizer 是管理员恢复操作,必须有 runbook 和审计记录。
|
||||||
|
|
||||||
|
## KubernetesExecutionParameters
|
||||||
|
|
||||||
|
这是 Kubernetes adapter 自己拥有的 cluster-scoped 管理员 API,不是 Job 用户 API。首版字段:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spec:
|
||||||
|
serviceAccountName: string
|
||||||
|
runtimeClassName: string
|
||||||
|
scheduling:
|
||||||
|
nodeSelector: map[string]string
|
||||||
|
tolerations: []Toleration
|
||||||
|
podSecurityContext: PodSecurityContext
|
||||||
|
imagePullPolicy: Always | IfNotPresent | Never
|
||||||
|
```
|
||||||
|
|
||||||
|
首版原生 `batch/v1 Job` 与 Ayatori Job 位于同一 namespace,因此 Secret/ConfigMap、ResourceQuota、
|
||||||
|
NetworkPolicy、日志和 owner reference 都保持原生语义。普通调用者只拥有 Ayatori Job 权限,
|
||||||
|
不应拥有修改生成的 batch Job/Pod 的权限。
|
||||||
|
|
||||||
|
`serviceAccountName` 是每个允许 namespace 中预先提供的同名 ServiceAccount。缺失时 Job 保持
|
||||||
|
未调度并报告原因,不回退到 `default` ServiceAccount。
|
||||||
|
|
||||||
|
参数允许使用 Kubernetes 强类型的 Toleration 和 PodSecurityContext,因为这是明确属于
|
||||||
|
Kubernetes adapter 的管理员 API;这不构成向 Job API 透传 PodSpec。
|
||||||
|
|
||||||
|
## OpenSandboxExecutionParameters
|
||||||
|
|
||||||
|
这是 OpenSandbox adapter 自己拥有的 cluster-scoped 管理员 API。首版字段:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spec:
|
||||||
|
endpoint: string
|
||||||
|
apiKeySecretRef:
|
||||||
|
namespace: string
|
||||||
|
name: string
|
||||||
|
key: string
|
||||||
|
poolRef: string
|
||||||
|
requestMapping: AdmissionOnly | Native
|
||||||
|
allowSecretEnv: bool
|
||||||
|
allowImageAuth: bool
|
||||||
|
```
|
||||||
|
|
||||||
|
- endpoint 必须为 HTTPS,Dev 显式允许的本地配置除外;不得包含认证信息。
|
||||||
|
- API key 只通过 namespaced Secret 引用,status/Event 不显示内容。
|
||||||
|
- poolRef 映射为 OpenSandbox 支持的 pool/profile 选择,不允许 Job 覆盖。
|
||||||
|
- `requestMapping=Native` 要求后端忠实接受 requests 与 limits;`AdmissionOnly` 表示 requests
|
||||||
|
只参与 Ayatori 准入,limits 映射为 OpenSandbox resourceLimits。
|
||||||
|
- Secret env 与 per-request image auth 都会使 adapter 读取 Kubernetes Secret 并把解析值发送
|
||||||
|
到 OpenSandbox API,必须由管理员分别显式启用。
|
||||||
|
|
||||||
|
OpenSandbox 参数不暴露任意 `extensions` map。未来确需使用某项 extension 时,将其提升为该
|
||||||
|
参数 CRD 中经过校验的命名字段。
|
||||||
|
|
||||||
|
## Condition 与 Job 状态机交互
|
||||||
|
|
||||||
|
Job 只有在以下条件同时满足时进入 `Accepted=True`:
|
||||||
|
|
||||||
|
- class 已按默认或显式名称解析;
|
||||||
|
- class UID 与已解析引用一致;
|
||||||
|
- class `Accepted=True`;
|
||||||
|
- namespace 符合 allowedNamespaces;
|
||||||
|
- Job resources 成功解析并处于允许范围;
|
||||||
|
- Job 使用的 Secret/ConfigMap 存在且可以按 optional 语义解析。
|
||||||
|
|
||||||
|
`JobClass Ready=False` 时,Job 保持 `Accepted=True`、`Scheduled=False`,等待后端恢复。
|
||||||
|
这样 class 配置合法性与当前可用性不会混成同一状态。
|
||||||
|
|
||||||
|
若 Job 已经 Scheduled,后续 class Ready 或 namespace label 变化不撤销执行。若 class 或参数
|
||||||
|
对象意外消失,controller 仍以 Job status 中的 controllerName、参数 UID 和 execution
|
||||||
|
references 尝试恢复;无法安全观察时进入非终态 `ResultUnknown`,不能切换 class 重跑。
|
||||||
|
|
||||||
|
## 测试矩阵
|
||||||
|
|
||||||
|
实现至少覆盖:
|
||||||
|
|
||||||
|
- 显式 class、namespace 默认和全局默认的优先级;
|
||||||
|
- 零个与多个全局默认 class;
|
||||||
|
- allowedNamespaces 允许、拒绝及接受后 label 变化;
|
||||||
|
- unsupported controllerName 和错误 parameters kind;
|
||||||
|
- parameters 不存在、稍后出现、UID 删除重建;
|
||||||
|
- resource defaults、min/max、request 大于 limit 和 quantity 边界;
|
||||||
|
- class policy 更新不改变已接受 Job 的 effective resources;
|
||||||
|
- class Ready=False 阻止新 Ensure,但不阻止现存执行 Observe/Cancel/Delete;
|
||||||
|
- Kubernetes ServiceAccount/runtime 配置缺失且不回退;
|
||||||
|
- OpenSandbox API key Secret 缺失、轮换及后端健康恢复;
|
||||||
|
- allowSecretEnv/allowImageAuth 拒绝不允许的 Job;
|
||||||
|
- class 删除被非终态 Job 阻止,终态清理后允许删除;
|
||||||
|
- 同名 class 或参数对象以新 UID 重建时不劫持现存 Job。
|
||||||
|
|
||||||
|
## 延后事项
|
||||||
|
|
||||||
|
- class 级并发和速率限制;
|
||||||
|
- Kueue LocalQueue/ClusterQueue 映射;
|
||||||
|
- capability-based 自动 class 选择;
|
||||||
|
- 多集群 Kubernetes executor;
|
||||||
|
- GPU、临时磁盘和其他扩展资源;
|
||||||
|
- workload identity 与 OpenSandbox Credential Vault;
|
||||||
|
- class 成本、优先级与抢占策略。
|
||||||
|
|
||||||
|
## 成熟实现参考
|
||||||
|
|
||||||
|
- [Kubernetes RuntimeClass](https://kubernetes.io/docs/concepts/containers/runtime-class/)
|
||||||
|
- [Kubernetes StorageClass](https://kubernetes.io/docs/concepts/storage/storage-classes/)
|
||||||
|
- [Gateway API GatewayClass](https://gateway-api.sigs.k8s.io/reference/api-types/gatewayclass/)
|
||||||
|
- [Kueue ResourceFlavor](https://kueue.sigs.k8s.io/docs/concepts/resource_flavor/)
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
# ADR-0004:采用可拆分的模块化 Controller 架构
|
||||||
|
|
||||||
|
- 状态:Accepted
|
||||||
|
- 日期:2026-09-17
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
Ayatori 将逐步提供任务执行、虚拟机、数据库、负载均衡、对象存储、托管 Kubernetes 和
|
||||||
|
人工操作等领域能力。这些能力拥有不同的生命周期、权限、网络位置和后端实现,若直接在
|
||||||
|
一个 controller 中相互调用并共享内部状态,后续接入新后端时容易形成代码耦合,也难以
|
||||||
|
独立扩缩容、发布和隔离故障。
|
||||||
|
|
||||||
|
另一方面,在首个领域能力完成前就拆分为多个独立服务,会立即引入镜像与部署管理、服务
|
||||||
|
间认证、版本兼容、分布式观测和故障处理成本,而这些成本尚未由真实运行需求证明。
|
||||||
|
|
||||||
|
Job 是首个领域对象。它既是最初的单次任务调度 API,也用于验证领域状态机与 Kubernetes
|
||||||
|
Job、OpenSandbox 和未来执行后端之间的适配边界。
|
||||||
|
|
||||||
|
## 决策
|
||||||
|
|
||||||
|
Ayatori 初期采用模块化单体:多个领域 controller 可以编译进同一个 controller manager,
|
||||||
|
但代码、API 所有权和依赖方向按照未来可独立部署的服务边界组织。
|
||||||
|
|
||||||
|
### 领域所有权
|
||||||
|
|
||||||
|
每个领域模块拥有自己的:
|
||||||
|
|
||||||
|
- CRD 与 API 版本;
|
||||||
|
- reconciler 和状态机;
|
||||||
|
- finalizer、conditions、删除及恢复语义;
|
||||||
|
- backend adapter contract;
|
||||||
|
- 领域测试。
|
||||||
|
|
||||||
|
初始领域包括但不限于 execution、compute、database、networking 和 human operations。领域
|
||||||
|
模块不得导入其他领域的内部实现,也不得直接修改其他领域所拥有对象的 spec 或 status。
|
||||||
|
|
||||||
|
### 跨领域协作
|
||||||
|
|
||||||
|
领域间的持久协作通过 Kubernetes API 对象、typed reference、owner reference 和
|
||||||
|
conditions 完成,而不是通过进程内 service 方法调用。
|
||||||
|
|
||||||
|
例如虚拟机完成创建后需要执行 provision,应创建或引用 Run 对象并观察其状态,而不是
|
||||||
|
直接调用 execution 模块的内部 Go API。更高层的资源组合由专门的领域对象或 GitOps 声明
|
||||||
|
完成,不引入统一包装所有底层能力的 Application controller。
|
||||||
|
|
||||||
|
该约束使 controller 即使暂时位于同一进程,其通信、失败和恢复行为仍与未来分进程部署
|
||||||
|
一致。
|
||||||
|
|
||||||
|
### Backend adapter
|
||||||
|
|
||||||
|
领域状态机只依赖本领域定义的最小 adapter contract,不依赖 Kubernetes Job、Crossplane、
|
||||||
|
OpenTofu、OpenSandbox 或具体厂商 SDK 类型。Adapter 负责:
|
||||||
|
|
||||||
|
- 幂等地确保期望外部资源存在;
|
||||||
|
- 观察并翻译外部状态;
|
||||||
|
- 执行取消、删除或 orphan 策略;
|
||||||
|
- 返回稳定的外部引用、能力和分类错误。
|
||||||
|
|
||||||
|
不同领域分别定义 adapter contract,不建立能包装所有资源类型的万能 Provider 接口。
|
||||||
|
后端不具备的能力必须显式报告,不通过虚假的统一语义隐藏差异。
|
||||||
|
|
||||||
|
Crossplane、OpenTofu 和 Ansible 等系统是可替换的 backend 实现或执行机制,不构成 Ayatori
|
||||||
|
面向用户的稳定 API。它们的 ProviderConfig、Workspace、playbook 等实现细节不得直接成为
|
||||||
|
领域 API 的必填契约。
|
||||||
|
|
||||||
|
### 共享代码
|
||||||
|
|
||||||
|
默认不建立跨领域的万能 service、repository 或 util 层。共享并非禁止,但必须来自已经
|
||||||
|
存在的真实重复,并同时满足:
|
||||||
|
|
||||||
|
1. 至少有两个真实调用者;
|
||||||
|
2. 重复的行为和语义一致,而不只是代码形状相似;
|
||||||
|
3. 调用方对其生命周期和预期演化方向一致;
|
||||||
|
4. 共享包不依赖任何具体领域的内部包。
|
||||||
|
|
||||||
|
适合共享的通常是机制,例如 conditions 操作、typed reference、重试退避、Secret 引用
|
||||||
|
读取、观测初始化和测试环境。领域状态机、资源策略、错误含义及后端选择不得为了消除少量
|
||||||
|
重复而抽取到共享层。
|
||||||
|
|
||||||
|
共享包使用表达具体职责的窄名称,初期保留在 `internal/shared/`。不建立内容持续膨胀的
|
||||||
|
通用 `util` 包,也不在实现稳定前承诺公共 Go API。
|
||||||
|
|
||||||
|
### 部署与拆分
|
||||||
|
|
||||||
|
controller manager 应支持按领域或 controller 集合选择性启用。初期可以使用一个二进制和
|
||||||
|
一个 Deployment;需要隔离时,优先使用同一制品部署为多个 Deployment。只有独立版本和
|
||||||
|
依赖关系成为真实需求后,才进一步拆分二进制或仓库。
|
||||||
|
|
||||||
|
出现下列任一情况时,应评估拆分部署:
|
||||||
|
|
||||||
|
- 需要独立扩缩容或显著不同的 reconcile 并发;
|
||||||
|
- 权限边界要求独立 ServiceAccount 与 RBAC;
|
||||||
|
- 后端只能从特定网络或节点访问;
|
||||||
|
- 沉重、不可信或冲突的 SDK 需要隔离;
|
||||||
|
- 一个领域的故障不应影响其他控制循环;
|
||||||
|
- 发布节奏或维护责任已经明确分离。
|
||||||
|
|
||||||
|
拆分不得改变领域 API,也不应将原本通过 API 对象完成的协作改为同步 RPC 链路。
|
||||||
|
|
||||||
|
## 结果
|
||||||
|
|
||||||
|
- 首个 Job 实现需要同时建立 execution 状态机和 adapter 边界,不能把 Kubernetes Job
|
||||||
|
细节写入领域模型。
|
||||||
|
- 初期避免承担不必要的微服务运维成本,同时保留按权限、网络位置和故障域拆分的路径。
|
||||||
|
- Kubernetes API 成为领域间异步协作和恢复边界,领域 controller 必须正确处理最终一致性。
|
||||||
|
- 部分机械代码会有意保留重复,直到共享语义被至少两个真实实现证明。
|
||||||
|
- 代码评审需要检查跨领域 import、对象写入所有权和后端类型泄漏。
|
||||||
|
- 当 Job 同时拥有 Kubernetes Job 与 OpenSandbox adapter 后,应复审 adapter contract,确认
|
||||||
|
它来自真实后端差异而非单一实现假设。
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
# ADR-0005:Job 使用短生命周期控制对象与 TTL 回收
|
||||||
|
|
||||||
|
- 状态:Accepted
|
||||||
|
- 日期:2026-09-17
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
Ayatori 的 `Job` 表达一次有限时长、有退出结果的执行。CI、基础设施 controller 和用户可能
|
||||||
|
持续创建大量 Job。若所有 Job CR、后端 Kubernetes Job、Pod、Event 和日志都长期保存在
|
||||||
|
Kubernetes 中,etcd、apiserver list/watch、controller cache、备份及恢复会持续承担历史
|
||||||
|
数据成本。
|
||||||
|
|
||||||
|
Kubernetes API 适合作为活动执行的在线控制与协调面,但不应在没有明确产品需求时兼任无限
|
||||||
|
增长的执行历史数据库。当前也尚未证明 Ayatori 必须独立于调用者提供长期历史或审计检索。
|
||||||
|
Gitea Actions 等调用方已经拥有自己的执行历史;基础设施 controller 也应把 Job 结果转化为
|
||||||
|
所属领域资源的 status。执行日志属于观测数据,应进入统一 observability 平台,而不是由
|
||||||
|
Job CR 或专用执行历史存储重复保存。
|
||||||
|
|
||||||
|
Kubernetes 原生 `batch/v1 Job` 使用独立的 TTL-after-finished controller 回收完成对象。
|
||||||
|
该 controller 只支持原生 Job,不能直接处理 Ayatori CRD,但其基于 informer 与延迟工作队列
|
||||||
|
的实现模式可以复用。
|
||||||
|
|
||||||
|
## 决策
|
||||||
|
|
||||||
|
Ayatori 提供 `execution.ayatori.ddupan.top` API group 下的 `Job` Kind。与 Kubernetes
|
||||||
|
`batch/v1 Job` 同名不构成冲突,完整 GVK 明确资源身份。
|
||||||
|
|
||||||
|
Ayatori Job 是短生命周期控制对象,不是永久执行记录。首版不要求将结果归档到 PostgreSQL,
|
||||||
|
也不引入 `Archived` condition 或 `JobRecord` CRD。
|
||||||
|
|
||||||
|
### 终态与 TTL
|
||||||
|
|
||||||
|
Job 到达成功、失败或取消终态后保留有限时间,随后由 Ayatori 自己的 TTL controller 删除。
|
||||||
|
API 提供与原生 Job 语义一致的 `spec.ttlSecondsAfterFinished`;未设置时是否允许无限保留由平台
|
||||||
|
准入策略决定,而不是隐式默认永久保存。
|
||||||
|
|
||||||
|
TTL 从 controller 写入的可信终态时间开始计算。TTL controller:
|
||||||
|
|
||||||
|
1. watch Job 的新增和更新;
|
||||||
|
2. 只处理已终止、设置 TTL 且尚未删除的对象;
|
||||||
|
3. 未到期时使用延迟工作队列在到期时间重新入队;
|
||||||
|
4. 到期时重新读取最新对象并复核 UID、终态和 TTL;
|
||||||
|
5. 使用 UID precondition 发起删除,防止删除同名重建对象;
|
||||||
|
6. 由 Job finalizer 完成实际执行后端与临时凭据清理。
|
||||||
|
|
||||||
|
Controller 重启后,informer 的初始 LIST 会重新触发现存对象的 reconcile 并重建内存中的延迟
|
||||||
|
任务,因此不以周期性全量扫描作为正确性基础。
|
||||||
|
|
||||||
|
### 结果消费
|
||||||
|
|
||||||
|
调用者必须在 TTL 窗口内观察 Job 终态,并把需要长期存在的业务事实写入自身状态。例如 VM
|
||||||
|
provision Job 成功后,VM controller 更新 `Provisioned` condition;之后删除 Job 不影响 VM
|
||||||
|
状态。CI 系统负责保存自身 workflow 历史。
|
||||||
|
|
||||||
|
Job status 只保存控制和短期诊断需要的结构化结果,不保存完整日志、大型输出或 artifact。
|
||||||
|
|
||||||
|
### 日志与 artifact
|
||||||
|
|
||||||
|
各 execution adapter 必须为执行实例注入稳定的关联信息,使 stdout/stderr 能由共享
|
||||||
|
observability 管道采集,并能够按 Ayatori Job 的 namespace、name 和 UID 查询。Job status
|
||||||
|
可以保存查询观测数据所需的关联标识或受控链接,但日志内容及其索引、保留和查询能力属于
|
||||||
|
observability 平台。
|
||||||
|
|
||||||
|
Job 与后端执行对象的 TTL 应为日志采集提供合理窗口,但 GC 不以日志归档成功作为前置条件,
|
||||||
|
避免观测平台故障阻塞控制面资源回收。日志采集延迟、丢失和后端不可用通过 observability
|
||||||
|
自身的监控和告警处理。
|
||||||
|
|
||||||
|
Artifact 与日志语义不同。调用者需要消费的构建产物、状态文件或结构化输出必须显式写入
|
||||||
|
对象存储等持久后端,并通过引用交付;不能依赖日志系统作为 artifact 存储。
|
||||||
|
|
||||||
|
### GitOps 边界
|
||||||
|
|
||||||
|
一次性 Job 不由 Flux 持续管理。否则 TTL 删除会被视为漂移并重新创建,从而重复执行。GitOps
|
||||||
|
可以管理 Job template、schedule、execution class 和策略;CI、CLI、UI 或其他 controller
|
||||||
|
通过 Kubernetes API 命令式创建 Job。
|
||||||
|
|
||||||
|
### 未来归档
|
||||||
|
|
||||||
|
只有出现长期历史查询、统一审计、调用者无法及时消费结果等真实需求时,才引入
|
||||||
|
外部 `JobRecord`/History API。届时可以为需要持久化的 retention policy 增加归档流程,并将
|
||||||
|
归档成功作为删除前置条件;不要求所有 Job 无条件承担该成本。
|
||||||
|
|
||||||
|
## 结果
|
||||||
|
|
||||||
|
- etcd 中只保留活动 Job、短期已完成 Job 和需要人工处理的异常 Job。
|
||||||
|
- 首版不依赖 PostgreSQL 和对象存储即可完成 Job 纵向切片。
|
||||||
|
- 调用者必须正确 watch 或轮询结果;TTL 配置必须为其提供足够消费窗口。
|
||||||
|
- 历史日志由共享 observability 平台查询,Job CR 只提供执行关联信息。
|
||||||
|
- Job 删除后的历史默认不可从 Kubernetes API 恢复,这是有意接受的语义。
|
||||||
|
- TTL controller 是 execution 领域的一部分,可以和其他 controller 编译、部署在同一个
|
||||||
|
controller manager 中。
|
||||||
|
- 若未来增加归档,应作为独立产品能力和 retention policy 演进,不改变 Job 作为短生命周期
|
||||||
|
控制对象的基本定位。
|
||||||
|
|
||||||
|
## 参考
|
||||||
|
|
||||||
|
- [Kubernetes Automatic Cleanup for Finished Jobs](https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/)
|
||||||
|
- [Kubernetes TTL-after-finished controller](https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/ttlafterfinished/ttlafterfinished_controller.go)
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
module git.ddupan.top/panxiao81/ayatori
|
||||||
|
|
||||||
|
go 1.27.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
k8s.io/api v0.37.0
|
||||||
|
k8s.io/apimachinery v0.37.0
|
||||||
|
k8s.io/client-go v0.37.0
|
||||||
|
sigs.k8s.io/controller-runtime v0.25.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
cel.dev/expr v0.25.1 // indirect
|
||||||
|
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||||
|
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
|
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
|
||||||
|
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
|
github.com/fxamacker/cbor/v2 v2.9.1 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/go-logr/zapr v1.3.0 // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v1.0.0 // indirect
|
||||||
|
github.com/go-openapi/jsonreference v1.0.0 // indirect
|
||||||
|
github.com/go-openapi/swag v0.27.1 // indirect
|
||||||
|
github.com/go-openapi/swag/cmdutils v0.27.1 // indirect
|
||||||
|
github.com/go-openapi/swag/conv v0.27.1 // indirect
|
||||||
|
github.com/go-openapi/swag/fileutils v0.27.1 // indirect
|
||||||
|
github.com/go-openapi/swag/jsonutils v0.27.1 // indirect
|
||||||
|
github.com/go-openapi/swag/loading v0.27.1 // indirect
|
||||||
|
github.com/go-openapi/swag/mangling v0.27.1 // indirect
|
||||||
|
github.com/go-openapi/swag/netutils v0.27.1 // indirect
|
||||||
|
github.com/go-openapi/swag/pools v0.27.1 // indirect
|
||||||
|
github.com/go-openapi/swag/stringutils v0.27.1 // indirect
|
||||||
|
github.com/go-openapi/swag/typeutils v0.27.1 // indirect
|
||||||
|
github.com/go-openapi/swag/yamlutils v0.27.1 // indirect
|
||||||
|
github.com/google/cel-go v0.29.2 // indirect
|
||||||
|
github.com/google/gnostic-models v0.7.0 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
|
github.com/prometheus/client_golang v1.24.0 // indirect
|
||||||
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
|
github.com/prometheus/common v0.70.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.21.1 // indirect
|
||||||
|
github.com/spf13/cobra v1.10.2 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.44.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.44.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/sdk v1.44.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.44.0 // indirect
|
||||||
|
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||||
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
go.uber.org/zap v1.27.1 // indirect
|
||||||
|
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
|
||||||
|
golang.org/x/net v0.57.0 // indirect
|
||||||
|
golang.org/x/oauth2 v0.36.0 // indirect
|
||||||
|
golang.org/x/sync v0.22.0 // indirect
|
||||||
|
golang.org/x/sys v0.47.0 // indirect
|
||||||
|
golang.org/x/term v0.45.0 // indirect
|
||||||
|
golang.org/x/text v0.40.0 // indirect
|
||||||
|
golang.org/x/time v0.15.0 // indirect
|
||||||
|
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||||
|
google.golang.org/grpc v1.82.1 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
|
||||||
|
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
|
||||||
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
|
k8s.io/apiextensions-apiserver v0.37.0 // indirect
|
||||||
|
k8s.io/apiserver v0.37.0 // indirect
|
||||||
|
k8s.io/component-base v0.37.0 // indirect
|
||||||
|
k8s.io/klog/v2 v2.140.0 // indirect
|
||||||
|
k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad // indirect
|
||||||
|
k8s.io/streaming v0.37.0 // indirect
|
||||||
|
k8s.io/utils v0.0.0-20260626114624-be93311217bd // indirect
|
||||||
|
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.36.0 // indirect
|
||||||
|
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||||
|
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||||
|
sigs.k8s.io/structured-merge-diff/v6 v6.4.2 // indirect
|
||||||
|
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||||
|
)
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
|
||||||
|
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||||
|
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||||
|
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||||
|
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
|
||||||
|
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
||||||
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||||
|
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||||
|
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||||
|
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
|
||||||
|
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||||
|
github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k=
|
||||||
|
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
|
||||||
|
github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
|
||||||
|
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
|
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
|
||||||
|
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
|
||||||
|
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
|
||||||
|
github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s=
|
||||||
|
github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y=
|
||||||
|
github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY=
|
||||||
|
github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0=
|
||||||
|
github.com/go-openapi/swag v0.27.1 h1:VotvOLWW8q/EAxB0YdsBBGC8XYyeL1YwBj2ungAGPNg=
|
||||||
|
github.com/go-openapi/swag v0.27.1/go.mod h1:GTkJPwHfhJp6MWr4/rCh64HVI3Ofu+tcsbfjfHmTxpE=
|
||||||
|
github.com/go-openapi/swag/cmdutils v0.27.1 h1:I7sYqaWVl5mq0NEmNQkAmFDyNin9ufvMX/p2zwtQaOE=
|
||||||
|
github.com/go-openapi/swag/cmdutils v0.27.1/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM=
|
||||||
|
github.com/go-openapi/swag/conv v0.27.1 h1:8wi9ZG+olmY1wXphl93EWniPtbSPkXM/feH7FgjsvrU=
|
||||||
|
github.com/go-openapi/swag/conv v0.27.1/go.mod h1:QbqMivkpKhC3g1B1GGGOJ6ANewI3S62dbzYu3Duowqs=
|
||||||
|
github.com/go-openapi/swag/fileutils v0.27.1 h1:QQqBSoi5mW4XpU85nS0mLcA+zAE6vLzrb0QkmLKf9oM=
|
||||||
|
github.com/go-openapi/swag/fileutils v0.27.1/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8=
|
||||||
|
github.com/go-openapi/swag/jsonutils v0.27.1 h1:SVgK3i4USzCU5mibOOS/l4ea2h9UQXy7J7RNLTjuXjU=
|
||||||
|
github.com/go-openapi/swag/jsonutils v0.27.1/go.mod h1:tdlEpZqdcQ17uj6J4YdK9vd8It5qWMwjWXOs0tjpRlk=
|
||||||
|
github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1 h1:mJu3COL9WEaZVp/Kf2PRMi7tPszPEJfSr/OO75ynCs8=
|
||||||
|
github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY=
|
||||||
|
github.com/go-openapi/swag/loading v0.27.1 h1:/DxUgDXKbBX4bcn7r9uEXfJyzN5XpiJmZplzQTjrRCY=
|
||||||
|
github.com/go-openapi/swag/loading v0.27.1/go.mod h1:jvGh3iA2+zyUUycB5fgJWzeHnhrpvGnJJM0RVE9ZShE=
|
||||||
|
github.com/go-openapi/swag/mangling v0.27.1 h1:yC9D0HyUE8gbP+BfmGx9+AA89ikwZTMjESK3OnnoaqA=
|
||||||
|
github.com/go-openapi/swag/mangling v0.27.1/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w=
|
||||||
|
github.com/go-openapi/swag/netutils v0.27.1 h1:mICMFoS82F5TZ4Zy3cqmcQk+BFeCp3Uyq3Np7GI0/qU=
|
||||||
|
github.com/go-openapi/swag/netutils v0.27.1/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ=
|
||||||
|
github.com/go-openapi/swag/pools v0.27.1 h1:9LeadcMyb2GJCbXX5hVQDbZ2Lq9TL4dCs/nx1j5DO0E=
|
||||||
|
github.com/go-openapi/swag/pools v0.27.1/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE=
|
||||||
|
github.com/go-openapi/swag/stringutils v0.27.1 h1:ZXePZ0r2p1qSjo8tD3Un4vFj8+FqlCkczxDrJIhYUp8=
|
||||||
|
github.com/go-openapi/swag/stringutils v0.27.1/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM=
|
||||||
|
github.com/go-openapi/swag/typeutils v0.27.1 h1:KSTdFlfnse4r6dP9IrEnwMldjE+zs71UeEB3//PtVXc=
|
||||||
|
github.com/go-openapi/swag/typeutils v0.27.1/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ=
|
||||||
|
github.com/go-openapi/swag/yamlutils v0.27.1 h1:ftxv6xvXb1E3zohUc+okZ9nSqNb9StQX/FXnKZ98sQA=
|
||||||
|
github.com/go-openapi/swag/yamlutils v0.27.1/go.mod h1:bnxFIB1qewGRiZHypXGZ3fNgf13/0HfRgnS/iZBDrOo=
|
||||||
|
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0=
|
||||||
|
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo=
|
||||||
|
github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug=
|
||||||
|
github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
|
||||||
|
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||||
|
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||||
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
|
github.com/google/cel-go v0.29.2 h1:ZtDxkeiMmz0mxbKDYiNkE5Lk7V5edMRcaaDf2jX002k=
|
||||||
|
github.com/google/cel-go v0.29.2/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8=
|
||||||
|
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
|
||||||
|
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||||
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/compress v1.19.0 h1:sXLILfc9jV2QYWkzFOPWStmcUVH2RHEB1JCdY2oVvCQ=
|
||||||
|
github.com/klauspost/compress v1.19.0/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
|
||||||
|
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.27.4 h1:fcEcQW/A++6aZAZQNUmNjvA9PSOzefMJBerHJ4t8v8Y=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.27.4/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||||
|
github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q=
|
||||||
|
github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI=
|
||||||
|
github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE=
|
||||||
|
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||||
|
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||||
|
github.com/prometheus/common v0.70.0 h1:bcpru3tWPVnxGnETLgOV5jbp/JRXgYEyv65CuBLAMMI=
|
||||||
|
github.com/prometheus/common v0.70.0/go.mod h1:S/SFasQmgGiYH6C81LKCtYa8QACgthGg5zxL2udV7SY=
|
||||||
|
github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI=
|
||||||
|
github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||||
|
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||||
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||||
|
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI=
|
||||||
|
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||||
|
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 h1:qazEJlUOQzhCpzQpFETGby7EdqjI1wsd0W+6Gg1SCTU=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0/go.mod h1:fOD2Yefuxixkx3ahVNf0O/PERb6r4OlbxfATVnYvzCo=
|
||||||
|
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||||
|
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
|
||||||
|
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
|
||||||
|
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||||
|
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
|
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||||
|
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
|
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
|
||||||
|
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
|
||||||
|
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
||||||
|
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
||||||
|
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
|
||||||
|
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
|
||||||
|
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||||
|
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||||
|
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||||
|
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
|
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||||
|
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
|
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
||||||
|
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
||||||
|
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
||||||
|
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
||||||
|
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||||
|
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||||
|
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
|
||||||
|
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
|
||||||
|
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
|
||||||
|
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
|
||||||
|
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||||
|
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||||
|
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
|
||||||
|
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
|
||||||
|
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
|
||||||
|
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
|
||||||
|
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
||||||
|
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||||
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
k8s.io/api v0.37.0 h1:Z//Vj9N7RA/yS2sDmxyeo7h+RR4zbUrd2vrd3Z0TbB4=
|
||||||
|
k8s.io/api v0.37.0/go.mod h1:LKXgcJWMc+f4OLbP5SFR8rulEg07zZhpi/zMULiBImk=
|
||||||
|
k8s.io/apiextensions-apiserver v0.37.0 h1:zRMQ3+/LIE5oZ0tVvXwYHC+dIkSP5cjNWju7AZU1LOI=
|
||||||
|
k8s.io/apiextensions-apiserver v0.37.0/go.mod h1:HU0PfSBwchHL5iDau6jjt9zU6ryWkDDlaVUiq91NK80=
|
||||||
|
k8s.io/apimachinery v0.37.0 h1:Np2AbDtf8x6RDHiD8T9LbKJ9gaegeVNa8yNm5FuGKm0=
|
||||||
|
k8s.io/apimachinery v0.37.0/go.mod h1:RN3nhprFSCxOi5Selxd7oMTXOe/c+ZbcE7Im+TS2zkE=
|
||||||
|
k8s.io/apiserver v0.37.0 h1:TXg7OxsOWrAH8J4Zi/gBAZuMw1Dfdd+6cca2h4qjRqo=
|
||||||
|
k8s.io/apiserver v0.37.0/go.mod h1:OddHDF4gy9qyIb8o/3+qaeP6S0vEObWLgOygVqXksv0=
|
||||||
|
k8s.io/client-go v0.37.0 h1:nsN31fy8wBySuZ+QRnKmrjRSQLOG2rvoGN0tKd12zhQ=
|
||||||
|
k8s.io/client-go v0.37.0/go.mod h1:FcGqw+Ll/gNQiq+nPGY1Oyt9y7SgDh1d3MW3RFDEbn0=
|
||||||
|
k8s.io/component-base v0.37.0 h1:3SdSa4+itMdFTDFTeR8CxKGmSTSMXFlKL4ky8OqjguM=
|
||||||
|
k8s.io/component-base v0.37.0/go.mod h1:LjOebp4R9y6LODWZQv102ZQxGheLcDO2ZJLAw6bbh4I=
|
||||||
|
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
|
||||||
|
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
|
||||||
|
k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A=
|
||||||
|
k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I=
|
||||||
|
k8s.io/streaming v0.37.0 h1:iPBUZLZiKt5bV+lxJurASMOV07VuBhNpiwJt2//AWrM=
|
||||||
|
k8s.io/streaming v0.37.0/go.mod h1:APlJR26ZWRcVy5bIEj0QRrKUXROtBHPcxl2NT7EAzPU=
|
||||||
|
k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs=
|
||||||
|
k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
||||||
|
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.36.0 h1:/YpDJ4vReG7ZmzSpBGxduXgywWkJU9zHubgJG03MT+Y=
|
||||||
|
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.36.0/go.mod h1:tJo1aepTXyR+8Xs3sUsGBDk4Ub2AM5dPAPKJx0mpm5c=
|
||||||
|
sigs.k8s.io/controller-runtime v0.25.0 h1:44KgRUPew331KSJpNu8zJow3iTR5W0p/SfrHdw3lV40=
|
||||||
|
sigs.k8s.io/controller-runtime v0.25.0/go.mod h1:4QqLdT6z/L6Olj8JJCtvztid4/fnIiYsfaTFScegctc=
|
||||||
|
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
|
||||||
|
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||||
|
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
|
||||||
|
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||||
|
sigs.k8s.io/structured-merge-diff/v6 v6.4.2 h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q=
|
||||||
|
sigs.k8s.io/structured-merge-diff/v6 v6.4.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||||
|
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||||
|
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package execution
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
executionv1alpha1 "git.ddupan.top/panxiao81/ayatori/api/execution/v1alpha1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JobState string
|
||||||
|
|
||||||
|
const (
|
||||||
|
JobStateResolving JobState = "Resolving"
|
||||||
|
JobStateScheduling JobState = "Scheduling"
|
||||||
|
JobStateStarting JobState = "Starting"
|
||||||
|
JobStateRunning JobState = "Running"
|
||||||
|
JobStateCancelling JobState = "Cancelling"
|
||||||
|
JobStateResultUnknown JobState = "ResultUnknown"
|
||||||
|
JobStateSucceeded JobState = "Succeeded"
|
||||||
|
JobStateFailed JobState = "Failed"
|
||||||
|
JobStateCancelled JobState = "Cancelled"
|
||||||
|
JobStateDeleting JobState = "Deleting"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StateOf derives a presentation state from durable API facts. The derived
|
||||||
|
// state is deliberately not persisted as a second source of truth.
|
||||||
|
func StateOf(job *executionv1alpha1.Job) JobState {
|
||||||
|
if !job.DeletionTimestamp.IsZero() {
|
||||||
|
return JobStateDeleting
|
||||||
|
}
|
||||||
|
|
||||||
|
succeeded := condition(job.Status.Conditions, executionv1alpha1.JobConditionSucceeded)
|
||||||
|
if succeeded != nil {
|
||||||
|
switch succeeded.Status {
|
||||||
|
case metav1.ConditionTrue:
|
||||||
|
return JobStateSucceeded
|
||||||
|
case metav1.ConditionFalse:
|
||||||
|
if succeeded.Reason == "Cancelled" {
|
||||||
|
return JobStateCancelled
|
||||||
|
}
|
||||||
|
return JobStateFailed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if job.Spec.DesiredState == executionv1alpha1.JobDesiredStateCancelled {
|
||||||
|
return JobStateCancelling
|
||||||
|
}
|
||||||
|
if succeeded != nil && succeeded.Status == metav1.ConditionUnknown && succeeded.Reason == "ResultUnknown" {
|
||||||
|
return JobStateResultUnknown
|
||||||
|
}
|
||||||
|
if job.Status.StartTime != nil {
|
||||||
|
return JobStateRunning
|
||||||
|
}
|
||||||
|
if conditionTrue(job.Status.Conditions, executionv1alpha1.JobConditionScheduled) {
|
||||||
|
return JobStateStarting
|
||||||
|
}
|
||||||
|
if conditionTrue(job.Status.Conditions, executionv1alpha1.JobConditionAccepted) {
|
||||||
|
return JobStateScheduling
|
||||||
|
}
|
||||||
|
return JobStateResolving
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateStatus checks invariants that every adapter must preserve.
|
||||||
|
func ValidateStatus(job *executionv1alpha1.Job) error {
|
||||||
|
succeeded := condition(job.Status.Conditions, executionv1alpha1.JobConditionSucceeded)
|
||||||
|
terminal := succeeded != nil && (succeeded.Status == metav1.ConditionTrue || succeeded.Status == metav1.ConditionFalse)
|
||||||
|
if terminal && job.Status.CompletionTime == nil {
|
||||||
|
return fmt.Errorf("terminal job must have completionTime")
|
||||||
|
}
|
||||||
|
if job.Status.CompletionTime != nil && !terminal {
|
||||||
|
return fmt.Errorf("completionTime requires a terminal Succeeded condition")
|
||||||
|
}
|
||||||
|
if job.Status.StartTime != nil && !conditionTrue(job.Status.Conditions, executionv1alpha1.JobConditionScheduled) {
|
||||||
|
return fmt.Errorf("startTime requires Scheduled=True")
|
||||||
|
}
|
||||||
|
if job.Status.StartTime != nil && job.Status.CompletionTime != nil && job.Status.CompletionTime.Before(job.Status.StartTime) {
|
||||||
|
return fmt.Errorf("completionTime must not precede startTime")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func condition(conditions []metav1.Condition, conditionType string) *metav1.Condition {
|
||||||
|
for i := range conditions {
|
||||||
|
if conditions[i].Type == conditionType {
|
||||||
|
return &conditions[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func conditionTrue(conditions []metav1.Condition, conditionType string) bool {
|
||||||
|
current := condition(conditions, conditionType)
|
||||||
|
return current != nil && current.Status == metav1.ConditionTrue
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package execution
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
executionv1alpha1 "git.ddupan.top/panxiao81/ayatori/api/execution/v1alpha1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStateOf(t *testing.T) {
|
||||||
|
now := metav1.NewTime(time.Now())
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
job executionv1alpha1.Job
|
||||||
|
want JobState
|
||||||
|
}{
|
||||||
|
{name: "resolving", want: JobStateResolving},
|
||||||
|
{name: "scheduling", job: jobWithConditions(newCondition(executionv1alpha1.JobConditionAccepted, metav1.ConditionTrue, "Valid")), want: JobStateScheduling},
|
||||||
|
{name: "starting", job: jobWithConditions(newCondition(executionv1alpha1.JobConditionScheduled, metav1.ConditionTrue, "BackendCreated")), want: JobStateStarting},
|
||||||
|
{name: "running", job: executionv1alpha1.Job{Status: executionv1alpha1.JobStatus{StartTime: &now}}, want: JobStateRunning},
|
||||||
|
{name: "result unknown", job: jobWithConditions(newCondition(executionv1alpha1.JobConditionSucceeded, metav1.ConditionUnknown, "ResultUnknown")), want: JobStateResultUnknown},
|
||||||
|
{name: "cancelling", job: executionv1alpha1.Job{Spec: executionv1alpha1.JobSpec{DesiredState: executionv1alpha1.JobDesiredStateCancelled}}, want: JobStateCancelling},
|
||||||
|
{name: "succeeded", job: jobWithConditions(newCondition(executionv1alpha1.JobConditionSucceeded, metav1.ConditionTrue, "Completed")), want: JobStateSucceeded},
|
||||||
|
{name: "failed", job: jobWithConditions(newCondition(executionv1alpha1.JobConditionSucceeded, metav1.ConditionFalse, "ExitCode")), want: JobStateFailed},
|
||||||
|
{name: "cancelled", job: jobWithConditions(newCondition(executionv1alpha1.JobConditionSucceeded, metav1.ConditionFalse, "Cancelled")), want: JobStateCancelled},
|
||||||
|
{name: "terminal beats desired cancellation", job: func() executionv1alpha1.Job {
|
||||||
|
j := jobWithConditions(newCondition(executionv1alpha1.JobConditionSucceeded, metav1.ConditionTrue, "Completed"))
|
||||||
|
j.Spec.DesiredState = executionv1alpha1.JobDesiredStateCancelled
|
||||||
|
return j
|
||||||
|
}(), want: JobStateSucceeded},
|
||||||
|
{name: "deleting beats terminal", job: func() executionv1alpha1.Job {
|
||||||
|
j := jobWithConditions(newCondition(executionv1alpha1.JobConditionSucceeded, metav1.ConditionTrue, "Completed"))
|
||||||
|
j.DeletionTimestamp = &now
|
||||||
|
return j
|
||||||
|
}(), want: JobStateDeleting},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := StateOf(&tt.job); got != tt.want {
|
||||||
|
t.Fatalf("StateOf() = %q, want %q", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateStatus(t *testing.T) {
|
||||||
|
start := metav1.NewTime(time.Unix(100, 0))
|
||||||
|
finish := metav1.NewTime(time.Unix(200, 0))
|
||||||
|
earlier := metav1.NewTime(time.Unix(50, 0))
|
||||||
|
scheduled := newCondition(executionv1alpha1.JobConditionScheduled, metav1.ConditionTrue, "BackendCreated")
|
||||||
|
succeeded := newCondition(executionv1alpha1.JobConditionSucceeded, metav1.ConditionTrue, "Completed")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
status executionv1alpha1.JobStatus
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{name: "empty status"},
|
||||||
|
{name: "running", status: executionv1alpha1.JobStatus{Conditions: []metav1.Condition{scheduled}, StartTime: &start}},
|
||||||
|
{name: "completed", status: executionv1alpha1.JobStatus{Conditions: []metav1.Condition{scheduled, succeeded}, StartTime: &start, CompletionTime: &finish}},
|
||||||
|
{name: "terminal without completion time", status: executionv1alpha1.JobStatus{Conditions: []metav1.Condition{succeeded}}, wantErr: true},
|
||||||
|
{name: "completion without terminal", status: executionv1alpha1.JobStatus{CompletionTime: &finish}, wantErr: true},
|
||||||
|
{name: "start without scheduling", status: executionv1alpha1.JobStatus{StartTime: &start}, wantErr: true},
|
||||||
|
{name: "completion before start", status: executionv1alpha1.JobStatus{Conditions: []metav1.Condition{scheduled, succeeded}, StartTime: &start, CompletionTime: &earlier}, wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := ValidateStatus(&executionv1alpha1.Job{Status: tt.status})
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Fatalf("ValidateStatus() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCondition(conditionType string, status metav1.ConditionStatus, reason string) metav1.Condition {
|
||||||
|
return metav1.Condition{Type: conditionType, Status: status, Reason: reason}
|
||||||
|
}
|
||||||
|
|
||||||
|
func jobWithConditions(conditions ...metav1.Condition) executionv1alpha1.Job {
|
||||||
|
return executionv1alpha1.Job{Status: executionv1alpha1.JobStatus{Conditions: conditions}}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user