mirror of
https://github.com/labring/sealos.git
synced 2026-09-01 15:38:06 +08:00
refactor(devbox): Devbox and related gateway services (#7098)
remove(devbox): Devbox and related gateway services
This commit is contained in:
@@ -1,317 +0,0 @@
|
||||
name: Build Service-RS Image
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
module:
|
||||
description: "Module name (e.g., httpgate)"
|
||||
required: true
|
||||
type: string
|
||||
push_image:
|
||||
description: "Push image"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
push_image_tag:
|
||||
description: "Push image tag"
|
||||
default: "latest"
|
||||
required: false
|
||||
type: string
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
module:
|
||||
description: "Rust service module (e.g., httpgate)"
|
||||
required: true
|
||||
type: string
|
||||
push_image:
|
||||
description: "Push image"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
push_image_tag:
|
||||
description: "Push image tag"
|
||||
default: "latest"
|
||||
required: false
|
||||
type: string
|
||||
|
||||
env:
|
||||
DEFAULT_OWNER: "labring"
|
||||
ALIYUN_REGISTRY: ${{ secrets.ALIYUN_REGISTRY }}
|
||||
ALIYUN_REPO_PREFIX: ${{ secrets.ALIYUN_REPO_PREFIX && secrets.ALIYUN_REPO_PREFIX || secrets.ALIYUN_USERNAME && format('{0}/{1}', secrets.ALIYUN_REGISTRY, secrets.ALIYUN_USERNAME) || '' }}
|
||||
|
||||
jobs:
|
||||
image-build:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- arch: amd64
|
||||
- arch: arm64
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: ${{ matrix.runs-on || 'ubuntu-24.04' }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set image repo
|
||||
id: set_repo
|
||||
env:
|
||||
MODULE: ${{ inputs.module }}
|
||||
REPOSITORY_OWNER: ${{ github.repository_owner }}
|
||||
run: |
|
||||
echo "GHCR_REPO=ghcr.io/${REPOSITORY_OWNER}/sealos-${MODULE}-service" >> $GITHUB_ENV
|
||||
if [[ -n "${{ env.ALIYUN_REPO_PREFIX }}" ]]; then
|
||||
echo "ALIYUN_REPO=${{ env.ALIYUN_REPO_PREFIX }}/sealos-${MODULE}-service" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.GHCR_REPO }}
|
||||
${{ env.ALIYUN_REPO }}
|
||||
labels: |
|
||||
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Github Container Hub
|
||||
if: ${{ inputs.push_image }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GHCR_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to Aliyun Registry
|
||||
if: ${{ inputs.push_image && env.ALIYUN_REGISTRY }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.ALIYUN_REGISTRY }}
|
||||
username: ${{ secrets.ALIYUN_USERNAME }}
|
||||
password: ${{ secrets.ALIYUN_PASSWORD }}
|
||||
|
||||
- name: Build
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./service-rs
|
||||
file: ./service-rs/${{ inputs.module }}/Dockerfile
|
||||
platforms: linux/${{ matrix.arch }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,"name=${{ env.GHCR_REPO }}${{ env.ALIYUN_REPO && format(',{0}', env.ALIYUN_REPO) || '' }}",name-canonical=true,push-by-digest=${{ inputs.push_image }},push=${{ inputs.push_image }}
|
||||
|
||||
- name: Export digest
|
||||
env:
|
||||
TEMP_DIR: ${{ runner.temp }}
|
||||
DIGEST: ${{ steps.build.outputs.digest }}
|
||||
run: |
|
||||
mkdir -p "${TEMP_DIR}/digests"
|
||||
touch "${TEMP_DIR}/digests/${DIGEST#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: digests-${{ inputs.module }}-${{ matrix.arch }}
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
image-release:
|
||||
name: Push Docker Images
|
||||
needs: image-build
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
if: ${{ inputs.push_image }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Login to Github Container Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GHCR_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to Aliyun Registry
|
||||
if: ${{ env.ALIYUN_REGISTRY }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.ALIYUN_REGISTRY }}
|
||||
username: ${{ secrets.ALIYUN_USERNAME }}
|
||||
password: ${{ secrets.ALIYUN_PASSWORD }}
|
||||
|
||||
- name: Set image repo
|
||||
id: set_repo
|
||||
env:
|
||||
MODULE: ${{ inputs.module }}
|
||||
REPOSITORY_OWNER: ${{ github.repository_owner }}
|
||||
run: |
|
||||
echo "GHCR_REPO=ghcr.io/${REPOSITORY_OWNER}/sealos-${MODULE}-service" >> $GITHUB_ENV
|
||||
if [[ -n "${{ env.ALIYUN_REPO_PREFIX }}" ]]; then
|
||||
echo "ALIYUN_REPO=${{ env.ALIYUN_REPO_PREFIX }}/sealos-${MODULE}-service" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: ${{ runner.temp }}/digests
|
||||
pattern: digests-${{ inputs.module }}-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.GHCR_REPO }}
|
||||
${{ env.ALIYUN_REPO }}
|
||||
tags: |
|
||||
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
|
||||
type=raw,value=${{ inputs.push_image_tag }},enable=${{ inputs.push_image_tag != '' && inputs.push_image_tag != 'latest' }}
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=sha
|
||||
labels: |
|
||||
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
||||
env:
|
||||
DOCKER_METADATA_SHORT_SHA_LENGTH: 9
|
||||
|
||||
- name: Create manifest list and push
|
||||
working-directory: ${{ runner.temp }}/digests
|
||||
env:
|
||||
GHCR_REPO: ${{ env.GHCR_REPO }}
|
||||
IMAGE_SOURCE: https://github.com/${{ github.repository }}
|
||||
run: |
|
||||
for TAG in $DOCKER_METADATA_OUTPUT_TAGS; do
|
||||
docker buildx imagetools create \
|
||||
--annotation "index:org.opencontainers.image.source=${IMAGE_SOURCE}" \
|
||||
-t $TAG \
|
||||
$(printf "${GHCR_REPO}@sha256:%s " *)
|
||||
sleep 5
|
||||
done
|
||||
|
||||
- name: Inspect image
|
||||
env:
|
||||
GHCR_REPO: ${{ env.GHCR_REPO }}
|
||||
IMAGE_VERSION: ${{ steps.meta.outputs.version }}
|
||||
run: |
|
||||
docker buildx imagetools inspect "${GHCR_REPO}:${IMAGE_VERSION}"
|
||||
|
||||
cluster-image-build:
|
||||
needs:
|
||||
- image-release
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ (github.event_name == 'push') || (github.event_name == 'create') || (inputs.push_image == true) }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set image repo
|
||||
env:
|
||||
MODULE: ${{ inputs.module }}
|
||||
REPOSITORY_OWNER: ${{ github.repository_owner }}
|
||||
run: |
|
||||
echo "MODULE_NAME=${MODULE}" >> $GITHUB_ENV
|
||||
# Docker image repo (always use GHCR for values.yaml to avoid Aliyun bandwidth costs)
|
||||
echo "GHCR_DOCKER_REPO=ghcr.io/${REPOSITORY_OWNER}/sealos-${MODULE}-service" >> $GITHUB_ENV
|
||||
# Cluster image repos
|
||||
echo "GHCR_CLUSTER_REPO=ghcr.io/${REPOSITORY_OWNER}/sealos-cloud-${MODULE}-service" >> $GITHUB_ENV
|
||||
if [[ -n "${{ env.ALIYUN_REPO_PREFIX }}" ]]; then
|
||||
echo "ALIYUN_CLUSTER_REPO=${{ env.ALIYUN_REPO_PREFIX }}/sealos-cloud-${MODULE}-service" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Docker meta for cluster image
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.GHCR_CLUSTER_REPO }}
|
||||
${{ env.ALIYUN_CLUSTER_REPO }}
|
||||
tags: |
|
||||
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
|
||||
type=raw,value=${{ inputs.push_image_tag }},enable=${{ inputs.push_image_tag != '' && inputs.push_image_tag != 'latest' }}
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=sha
|
||||
env:
|
||||
DOCKER_METADATA_SHORT_SHA_LENGTH: 9
|
||||
|
||||
- name: Install sealos
|
||||
run: |
|
||||
sudo bash ./.github/scripts/install.sh
|
||||
|
||||
- name: Build ${{ env.MODULE_NAME }} cluster image
|
||||
working-directory: service-rs/${{ inputs.module }}/deploy
|
||||
env:
|
||||
MODULE_NAME: ${{ env.MODULE_NAME }}
|
||||
GHCR_DOCKER_REPO: ${{ env.GHCR_DOCKER_REPO }}
|
||||
IMAGE_SOURCE: https://github.com/${{ github.repository }}
|
||||
run: |
|
||||
# Build cluster images for each tag (amd64)
|
||||
for TAG in $DOCKER_METADATA_OUTPUT_TAGS; do
|
||||
# Update image in values.yaml - always use GHCR to avoid Aliyun bandwidth costs
|
||||
if [[ -f charts/${MODULE_NAME}/values.yaml ]]; then
|
||||
IMAGE_TAG="${TAG##*:}"
|
||||
echo "Updating image in charts/${MODULE_NAME}/values.yaml to ${GHCR_DOCKER_REPO}:${IMAGE_TAG}"
|
||||
sed -i "s|repository:.*|repository: ${GHCR_DOCKER_REPO}|" charts/${MODULE_NAME}/values.yaml
|
||||
sed -i "s|tag:.*|tag: \"${IMAGE_TAG}\"|" charts/${MODULE_NAME}/values.yaml
|
||||
fi
|
||||
sudo rm -rf registry
|
||||
echo "Building ${TAG}-amd64"
|
||||
sudo sealos build -t "${TAG}-amd64" --platform linux/amd64 --label "org.opencontainers.image.source=${IMAGE_SOURCE}" -f Kubefile
|
||||
done
|
||||
|
||||
# Build cluster images for each tag (arm64)
|
||||
for TAG in $DOCKER_METADATA_OUTPUT_TAGS; do
|
||||
if [[ -f charts/${MODULE_NAME}/values.yaml ]]; then
|
||||
IMAGE_TAG="${TAG##*:}"
|
||||
sed -i "s|repository:.*|repository: ${GHCR_DOCKER_REPO}|" charts/${MODULE_NAME}/values.yaml
|
||||
sed -i "s|tag:.*|tag: \"${IMAGE_TAG}\"|" charts/${MODULE_NAME}/values.yaml
|
||||
fi
|
||||
sudo rm -rf registry
|
||||
echo "Building ${TAG}-arm64"
|
||||
sudo sealos build -t "${TAG}-arm64" --platform linux/arm64 --label "org.opencontainers.image.source=${IMAGE_SOURCE}" -f Kubefile
|
||||
done
|
||||
|
||||
- name: Sealos login to ghcr.io
|
||||
env:
|
||||
REPOSITORY_OWNER: ${{ github.repository_owner }}
|
||||
GH_PAT: ${{ secrets.GHCR_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
sudo sealos login -u "$REPOSITORY_OWNER" -p "$GH_PAT" --debug ghcr.io
|
||||
|
||||
- name: Sealos login to Aliyun Registry
|
||||
if: ${{ env.ALIYUN_REGISTRY }}
|
||||
env:
|
||||
ALIYUN_USERNAME: ${{ secrets.ALIYUN_USERNAME }}
|
||||
ALIYUN_PASSWORD: ${{ secrets.ALIYUN_PASSWORD }}
|
||||
run: |
|
||||
sudo sealos login -u "$ALIYUN_USERNAME" -p "$ALIYUN_PASSWORD" --debug ${{ env.ALIYUN_REGISTRY }}
|
||||
|
||||
- name: Manifest Cluster Images
|
||||
run: |
|
||||
sudo sealos images
|
||||
for TAG in $DOCKER_METADATA_OUTPUT_TAGS; do
|
||||
echo "Creating manifest for ${TAG}"
|
||||
bash scripts/manifest-cluster-images.sh "$TAG"
|
||||
done
|
||||
@@ -1,63 +0,0 @@
|
||||
name: Build Service-RS Images
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
push_image:
|
||||
description: "Push image"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
push_image_tag:
|
||||
description: "Push image tag"
|
||||
default: "latest"
|
||||
required: false
|
||||
type: string
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
push_image:
|
||||
description: "Push image"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
push_image_tag:
|
||||
description: "Push image tag"
|
||||
default: "latest"
|
||||
required: false
|
||||
type: string
|
||||
push:
|
||||
branches: ["*"]
|
||||
paths:
|
||||
- "service-rs/**"
|
||||
- ".github/workflows/service-rs.yml"
|
||||
- ".github/workflows/service-rs-build.yml"
|
||||
- "!**/*.md"
|
||||
pull_request:
|
||||
branches: ["*"]
|
||||
paths:
|
||||
- "service-rs/**"
|
||||
- ".github/workflows/service-rs.yml"
|
||||
- ".github/workflows/service-rs-build.yml"
|
||||
- "!**/*.md"
|
||||
|
||||
# Avoid using ${{ github.workflow }} - when called via workflow_call, it inherits the caller's name causing conflicts
|
||||
concurrency:
|
||||
group: service-rs-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
image-build:
|
||||
uses: ./.github/workflows/service-rs-build.yml
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
secrets: inherit
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
module:
|
||||
- httpgate
|
||||
with:
|
||||
module: ${{ matrix.module }}
|
||||
push_image: ${{ (github.event_name == 'push') || (github.event_name == 'create') || (inputs.push_image == true) }}
|
||||
push_image_tag: ${{ inputs.push_image_tag }}
|
||||
@@ -1,3 +0,0 @@
|
||||
# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file
|
||||
# Ignore build and test binaries.
|
||||
#bin/
|
||||
@@ -1,30 +0,0 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
bin/*
|
||||
Dockerfile.cross
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
|
||||
# Kubernetes Generated files - skip generated files, except for vendored files
|
||||
!vendor/**/zz_generated.*
|
||||
|
||||
# editor and IDE paraphernalia
|
||||
.idea
|
||||
.vscode
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# ignore deploy.yaml
|
||||
deploy/manifests/deploy.yaml
|
||||
@@ -1,22 +0,0 @@
|
||||
# Copyright © 2023 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
FROM gcr.io/distroless/static:nonroot
|
||||
ARG TARGETARCH
|
||||
|
||||
WORKDIR /
|
||||
USER 65532:65532
|
||||
|
||||
COPY bin/controller-devbox-$TARGETARCH /manager
|
||||
ENTRYPOINT ["/manager"]
|
||||
@@ -1,146 +0,0 @@
|
||||
|
||||
# Image URL to use all building/pushing image targets
|
||||
IMG ?= ghcr.io/labring/sealos-devbox-controller:latest
|
||||
TARGETARCH ?= amd64
|
||||
|
||||
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
|
||||
ENVTEST_K8S_VERSION = 1.28.0
|
||||
|
||||
# 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
|
||||
|
||||
# Setting SHELL to bash allows bash commands to be executed by recipes.
|
||||
# This is a requirement for 'setup-envtest.sh' in the test target.
|
||||
# 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 commands 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 devbox 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:headerFile="hack/boilerplate.go.txt" 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 envtest ## Run tests.
|
||||
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -coverprofile cover.out
|
||||
|
||||
##@ Build
|
||||
|
||||
.PHONY: build
|
||||
build: ## Build manager binary.
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -ldflags "-s -w" -trimpath -o bin/manager cmd/main.go
|
||||
|
||||
.PHONY: run
|
||||
run: manifests generate fmt vet ## Run a controller from your host.
|
||||
go run ./cmd/main.go
|
||||
|
||||
.PHONY: debug
|
||||
debug:
|
||||
go run ./cmd/main.go --debug
|
||||
|
||||
.PHONY: docker-build
|
||||
docker-build: ## Build docker image with the manager.
|
||||
mv bin/manager bin/controller-devbox-${TARGETARCH}
|
||||
chmod +x bin/controller-devbox-${TARGETARCH}
|
||||
docker build -t ${IMG} . --build-arg TARGETARCH=${TARGETARCH}
|
||||
|
||||
.PHONY: docker-push
|
||||
docker-push: ## Push docker image with the manager.
|
||||
docker push ${IMG}
|
||||
|
||||
##@ 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.
|
||||
$(KUSTOMIZE) build config/crd | kubectl apply -f -
|
||||
|
||||
.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.
|
||||
$(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f -
|
||||
|
||||
.PHONY: pre-deploy
|
||||
pre-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 > deploy/manifests/deploy.yaml
|
||||
|
||||
.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: ## 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 -
|
||||
|
||||
##@ Build Dependencies
|
||||
|
||||
## Location to install dependencies to
|
||||
LOCALBIN ?= $(shell pwd)/bin
|
||||
$(LOCALBIN):
|
||||
mkdir -p $(LOCALBIN)
|
||||
|
||||
## Tool Binaries
|
||||
KUSTOMIZE ?= $(LOCALBIN)/kustomize
|
||||
CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
|
||||
ENVTEST ?= $(LOCALBIN)/setup-envtest
|
||||
|
||||
## Tool Versions
|
||||
KUSTOMIZE_VERSION ?= v5.3.0
|
||||
CONTROLLER_TOOLS_VERSION ?= v0.18.0
|
||||
|
||||
KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"
|
||||
.PHONY: kustomize
|
||||
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
|
||||
$(KUSTOMIZE): $(LOCALBIN)
|
||||
curl -s $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN)
|
||||
|
||||
.PHONY: controller-gen
|
||||
controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.
|
||||
$(CONTROLLER_GEN): $(LOCALBIN)
|
||||
GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION)
|
||||
|
||||
.PHONY: envtest
|
||||
envtest: $(ENVTEST) ## Download envtest-setup locally if necessary.
|
||||
$(ENVTEST): $(LOCALBIN)
|
||||
GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest
|
||||
@@ -1,38 +0,0 @@
|
||||
# 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
|
||||
domain: sealos.io
|
||||
layout:
|
||||
- go.kubebuilder.io/v4
|
||||
projectName: devbox
|
||||
repo: github.com/labring/sealos/controllers/devbox
|
||||
resources:
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: true
|
||||
controller: true
|
||||
domain: sealos.io
|
||||
group: devbox
|
||||
kind: Devbox
|
||||
path: github.com/labring/sealos/controllers/devbox/api/v1alpha2
|
||||
version: v1alpha2
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: true
|
||||
controller: true
|
||||
domain: sealos.io
|
||||
group: devbox
|
||||
kind: DevBoxReleases
|
||||
path: github.com/labring/sealos/controllers/devbox/api/v1alpha2
|
||||
version: v1alpha2
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: true
|
||||
controller: true
|
||||
domain: sealos.io
|
||||
group: devbox
|
||||
kind: DevBoxRelease
|
||||
path: github.com/labring/sealos/controllers/devbox/api/v1alpha2
|
||||
version: v1alpha2
|
||||
version: "3"
|
||||
@@ -1,115 +0,0 @@
|
||||
# devbox
|
||||
// TODO(user): Add simple overview of use/purpose
|
||||
|
||||
## Description
|
||||
// TODO(user): An in-depth paragraph about your project and overview of use
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
- go version v1.22.0+
|
||||
- docker version 17.03+.
|
||||
- kubectl version v1.11.3+.
|
||||
- Access to a Kubernetes v1.11.3+ cluster.
|
||||
|
||||
### To Deploy on the cluster
|
||||
**Build and push your image to the location specified by `IMG`:**
|
||||
|
||||
```sh
|
||||
make docker-build docker-push IMG=<some-registry>/devbox:tag
|
||||
```
|
||||
|
||||
**NOTE:** This image ought to be published in the personal registry you specified.
|
||||
And it is required to have access to pull the image from the working environment.
|
||||
Make sure you have the proper permission to the registry if the above commands don’t work.
|
||||
|
||||
**Install the CRDs into the cluster:**
|
||||
|
||||
```sh
|
||||
make install
|
||||
```
|
||||
|
||||
**Deploy the Manager to the cluster with the image specified by `IMG`:**
|
||||
|
||||
```sh
|
||||
make deploy IMG=<some-registry>/devbox:tag
|
||||
```
|
||||
|
||||
> **NOTE**: If you encounter RBAC errors, you may need to grant yourself cluster-admin
|
||||
privileges or be logged in as admin.
|
||||
|
||||
**Create instances of your solution**
|
||||
You can apply the samples (examples) from the config/sample:
|
||||
|
||||
```sh
|
||||
kubectl apply -k config/samples/
|
||||
```
|
||||
|
||||
>**NOTE**: Ensure that the samples has default values to test it out.
|
||||
|
||||
### To Uninstall
|
||||
**Delete the instances (CRs) from the cluster:**
|
||||
|
||||
```sh
|
||||
kubectl delete -k config/samples/
|
||||
```
|
||||
|
||||
**Delete the APIs(CRDs) from the cluster:**
|
||||
|
||||
```sh
|
||||
make uninstall
|
||||
```
|
||||
|
||||
**UnDeploy the controller from the cluster:**
|
||||
|
||||
```sh
|
||||
make undeploy
|
||||
```
|
||||
|
||||
## Project Distribution
|
||||
|
||||
Following are the steps to build the installer and distribute this project to users.
|
||||
|
||||
1. Build the installer for the image built and published in the registry:
|
||||
|
||||
```sh
|
||||
make build-installer IMG=<some-registry>/devbox:tag
|
||||
```
|
||||
|
||||
NOTE: The makefile target mentioned above generates an 'install.yaml'
|
||||
file in the dist directory. This file contains all the resources built
|
||||
with Kustomize, which are necessary to install this project without
|
||||
its dependencies.
|
||||
|
||||
2. Using the installer
|
||||
|
||||
Users can just run kubectl apply -f <URL for YAML BUNDLE> to install the project, i.e.:
|
||||
|
||||
```sh
|
||||
kubectl apply -f https://raw.githubusercontent.com/<org>/devbox/<tag or branch>/dist/install.yaml
|
||||
```
|
||||
|
||||
## Contributing
|
||||
// TODO(user): Add detailed information on how you would like others to contribute to this project
|
||||
|
||||
**NOTE:** Run `make help` for more information on all potential `make` targets
|
||||
|
||||
More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2024.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
# devbox-controller
|
||||
@@ -1,57 +0,0 @@
|
||||
package v1alpha2
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Devbox condition types.
|
||||
//
|
||||
// These are intentionally minimal for now: they provide observability and a stable
|
||||
// surface for future state-machine hardening (e.g. preventing commit from being
|
||||
// skipped due to concurrent status updates).
|
||||
const (
|
||||
// DevboxConditionStateTransitionPending indicates spec.state != status.state and
|
||||
// a transition is pending processing by the state-change handler.
|
||||
DevboxConditionStateTransitionPending = "StateTransitionPending"
|
||||
|
||||
// DevboxConditionCommitInProgress indicates a commit workflow is in progress for
|
||||
// a transition (typically Running/Paused -> Shutdown/Stopped).
|
||||
DevboxConditionCommitInProgress = "CommitInProgress"
|
||||
|
||||
// Resource sync conditions (controller reconcile steps).
|
||||
DevboxConditionSecretSynced = "SecretSynced"
|
||||
DevboxConditionStartupConfigMapSynced = "StartupConfigMapSynced"
|
||||
DevboxConditionNetworkSynced = "NetworkSynced"
|
||||
DevboxConditionPodSynced = "PodSynced"
|
||||
DevboxConditionPhaseSynced = "PhaseSynced"
|
||||
)
|
||||
|
||||
// Devbox condition reasons.
|
||||
const (
|
||||
DevboxReasonSpecStateChanged = "SpecStateChanged"
|
||||
DevboxReasonCommitStarted = "CommitStarted"
|
||||
DevboxReasonCommitSucceeded = "CommitSucceeded"
|
||||
DevboxReasonCommitFailed = "CommitFailed"
|
||||
DevboxReasonStateTransitionSynced = "StateTransitionSynced"
|
||||
DevboxReasonCommitNotInProgress = "CommitNotInProgress"
|
||||
|
||||
DevboxReasonSyncSucceeded = "SyncSucceeded"
|
||||
DevboxReasonSyncFailed = "SyncFailed"
|
||||
DevboxReasonNotConfigured = "NotConfigured"
|
||||
)
|
||||
|
||||
// SetCondition sets (or updates) a status condition on the Devbox.
|
||||
func (d *Devbox) SetCondition(cond metav1.Condition) {
|
||||
meta.SetStatusCondition(&d.Status.Conditions, cond)
|
||||
}
|
||||
|
||||
// GetCondition returns the condition pointer if present.
|
||||
func (d *Devbox) GetCondition(conditionType string) *metav1.Condition {
|
||||
for i := range d.Status.Conditions {
|
||||
if d.Status.Conditions[i].Type == conditionType {
|
||||
return &d.Status.Conditions[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,317 +0,0 @@
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1alpha2
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
// FinalizerName is the finalizer for Devbox
|
||||
FinalizerName = "devbox.sealos.io/finalizer"
|
||||
|
||||
// Annotate the devbox pod with the devbox init
|
||||
AnnotationInit = "devbox.sealos.io/init"
|
||||
// Annotate the devbox pod with the storage limit
|
||||
AnnotationStorageLimit = "devbox.sealos.io/storage-limit"
|
||||
// Annotate the devbox pod with the devbox part of
|
||||
AnnotationContentID = "devbox.sealos.io/content-id"
|
||||
// Annotate the devbox node with container filesystem threshold
|
||||
AnnotationContainerFSAvailableThreshold = "devbox.sealos.io/container-fs-available-threshold"
|
||||
// Annotate the devbox node with cpu request and limit ratio
|
||||
AnnotationCPURequestRatio = "devbox.sealos.io/cpu-request-ratio"
|
||||
AnnotationCPULimitRatio = "devbox.sealos.io/cpu-limit-ratio"
|
||||
// Annotate the devbox node with memory request and limit ratio
|
||||
AnnotationMemoryRequestRatio = "devbox.sealos.io/memory-request-ratio"
|
||||
AnnotationMemoryLimitRatio = "devbox.sealos.io/memory-limit-ratio"
|
||||
// Annotate the devbox pod with the runtime
|
||||
AnnotationRuntime = "io.containerd.cri.runtime-handler"
|
||||
// Annotate the devbox pod with the blockio resources
|
||||
AnnotationBlockIOResources = "blockio.resources.beta.kubernetes.io/pod"
|
||||
|
||||
// Label the devbox pod with the devbox part of
|
||||
LabelDevBoxPartOf = "devbox"
|
||||
// Index for pod node name
|
||||
PodNodeNameIndex = "spec.nodeName"
|
||||
// Pod runtime handler for devbox pod
|
||||
PodRuntimeHandler = "devbox-runc"
|
||||
)
|
||||
|
||||
type DevboxState string
|
||||
|
||||
const (
|
||||
// DevboxStateRunning means the Devbox is running
|
||||
DevboxStateRunning DevboxState = "Running"
|
||||
// DevboxStatePending means the Devbox is pending
|
||||
DevboxStatePending DevboxState = "Pending"
|
||||
// DevboxStatePaused means the Devbox is paused, pod will be released but content lv and nodeport service will be retained
|
||||
DevboxStatePaused DevboxState = "Paused"
|
||||
// DevboxStateStopped means the Devbox is stopped, pod and content lv will be released but nodeport service will be retained
|
||||
DevboxStateStopped DevboxState = "Stopped"
|
||||
// DevboxStateShutdown means the devbox is shutdown, pod, content lv and nodeport service will be released
|
||||
DevboxStateShutdown DevboxState = "Shutdown"
|
||||
)
|
||||
|
||||
type NetworkType string
|
||||
|
||||
const (
|
||||
NetworkTypeNodePort NetworkType = "NodePort"
|
||||
NetworkTypeTailnet NetworkType = "Tailnet"
|
||||
NetworkTypeSSHGate NetworkType = "SSHGate"
|
||||
)
|
||||
|
||||
type RuntimeRef struct {
|
||||
// +kubebuilder:validation:Required
|
||||
Name string `json:"name"`
|
||||
// +kubebuilder:validation:Optional
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
type NetworkSpec struct {
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:Enum=NodePort;Tailnet;SSHGate
|
||||
Type NetworkType `json:"type"`
|
||||
// +kubebuilder:validation:Optional
|
||||
ExtraPorts []corev1.ContainerPort `json:"extraPorts,omitempty"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
// +kubebuilder:validation:Optional
|
||||
// +kubebuilder:default=devbox
|
||||
User string `json:"user"`
|
||||
|
||||
// +kubebuilder:validation:Optional
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
|
||||
// +kubebuilder:validation:Optional
|
||||
Command []string `json:"command,omitempty"`
|
||||
// kubebuilder:validation:Optional
|
||||
Args []string `json:"args,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
// +kubebuilder:default=/home/devbox/project
|
||||
WorkingDir string `json:"workingDir,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
Env []corev1.EnvVar `json:"env,omitempty"`
|
||||
|
||||
// +kubebuilder:validation:Optional
|
||||
// +kubebuilder:default={/bin/bash,-c}
|
||||
ReleaseCommand []string `json:"releaseCommand,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
// +kubebuilder:default={/home/devbox/project/entrypoint.sh}
|
||||
ReleaseArgs []string `json:"releaseArgs,omitempty"`
|
||||
|
||||
// TODO: in v1alpha2 api we need fix the port and app port into one field and create a new type for it.
|
||||
// +kubebuilder:validation:Optional
|
||||
// +kubebuilder:default={{name:"devbox-ssh-port",containerPort:22,protocol:TCP}}
|
||||
Ports []corev1.ContainerPort `json:"ports,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
// +kubebuilder:default={{name:"devbox-app-port",port:8080,protocol:TCP}}
|
||||
AppPorts []corev1.ServicePort `json:"appPorts,omitempty"`
|
||||
|
||||
// +kubebuilder:validation:Optional
|
||||
VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
Volumes []corev1.Volume `json:"volumes,omitempty"`
|
||||
}
|
||||
|
||||
// DevboxSpec defines the desired state of Devbox
|
||||
type DevboxSpec struct {
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:Enum=Running;Paused;Stopped;Shutdown
|
||||
// +kubebuilder:default=Running
|
||||
State DevboxState `json:"state"`
|
||||
// +kubebuilder:validation:Required
|
||||
Resource corev1.ResourceList `json:"resource"`
|
||||
|
||||
// +kubebuilder:validation:Required
|
||||
Image string `json:"image"`
|
||||
|
||||
// +kubebuilder:validation:Optional
|
||||
TemplateID string `json:"templateID"`
|
||||
|
||||
// +kubebuilder:validation:Required
|
||||
Config Config `json:"config"`
|
||||
|
||||
// +kubebuilder:validation:Optional
|
||||
// devbox storage limit, `storageLimit` will be used to generate the devbox pod label.
|
||||
StorageLimit string `json:"storageLimit,omitempty"`
|
||||
|
||||
// +kubebuilder:validation:Required
|
||||
NetworkSpec NetworkSpec `json:"network,omitempty"`
|
||||
|
||||
// +kubebuilder:validation:Optional
|
||||
RuntimeClassName string `json:"runtimeClassName,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
Affinity *corev1.Affinity `json:"affinity,omitempty"`
|
||||
}
|
||||
|
||||
type NetworkStatus struct {
|
||||
// +kubebuilder:validation:Optional
|
||||
// +kubebuilder:validation:Enum=NodePort;Tailnet;SSHGate
|
||||
Type NetworkType `json:"type,omitempty"`
|
||||
|
||||
// +kubebuilder:validation:Optional
|
||||
NodePort int32 `json:"nodePort,omitempty"`
|
||||
|
||||
// +kubebuilder:validation:Optional
|
||||
UniqueID string `json:"uniqueID,omitempty"`
|
||||
|
||||
// todo TailNet
|
||||
// +kubebuilder:validation:Optional
|
||||
TailNet string `json:"tailnet,omitempty"`
|
||||
}
|
||||
|
||||
type CommitStatus string
|
||||
|
||||
const (
|
||||
CommitStatusSuccess CommitStatus = "Success"
|
||||
CommitStatusFailed CommitStatus = "Failed"
|
||||
CommitStatusPending CommitStatus = "Pending"
|
||||
CommitStatusCommitting CommitStatus = "Committing"
|
||||
)
|
||||
|
||||
type DevboxPhase string
|
||||
|
||||
const (
|
||||
// DevboxPhaseRunning means Devbox is run and run success
|
||||
DevboxPhaseRunning DevboxPhase = "Running"
|
||||
// DevboxPhasePending means Devbox is run but not run success
|
||||
DevboxPhasePending DevboxPhase = "Pending"
|
||||
// DevboxPhasePaused means Devbox is paused and paused success
|
||||
DevboxPhasePaused DevboxPhase = "Paused"
|
||||
// DevboxPhasePausing means Devbox is pausing
|
||||
DevboxPhasePausing DevboxPhase = "Pausing"
|
||||
// DevboxPhaseStopped means Devbox is stop and stopped success
|
||||
DevboxPhaseStopped DevboxPhase = "Stopped"
|
||||
// DevboxPhaseStopping means Devbox is stopping
|
||||
DevboxPhaseStopping DevboxPhase = "Stopping"
|
||||
// DevboxPhaseShutdown means Devbox is shutdown and service is deleted
|
||||
DevboxPhaseShutdown DevboxPhase = "Shutdown"
|
||||
// DevboxPhaseShutting means Devbox is shutting
|
||||
DevboxPhaseShutting DevboxPhase = "Shutting"
|
||||
// DevboxPhaseError means Devbox is error
|
||||
DevboxPhaseError DevboxPhase = "Error"
|
||||
// DevboxPhaseUnknown means Devbox is unknown
|
||||
DevboxPhaseUnknown DevboxPhase = "Unknown"
|
||||
)
|
||||
|
||||
type CommitRecord struct {
|
||||
// BaseImage is the image of the that devbox is running on
|
||||
// +kubebuilder:validation:Optional
|
||||
BaseImage string `json:"baseImage"`
|
||||
|
||||
// CommitImage is the image of the that devbox is committed to
|
||||
// +kubebuilder:validation:Optional
|
||||
CommitImage string `json:"commitImage"`
|
||||
|
||||
// Node is the node name
|
||||
// +kubebuilder:validation:Optional
|
||||
Node string `json:"node"`
|
||||
|
||||
// GenerateTime is the time when the commit is generated
|
||||
// +kubebuilder:validation:Optional
|
||||
GenerateTime metav1.Time `json:"generateTime"`
|
||||
|
||||
// ScheduleTime is the time when the commit is scheduled
|
||||
// +kubebuilder:validation:Optional
|
||||
ScheduleTime metav1.Time `json:"scheduleTime"`
|
||||
|
||||
// UpdateTime is the time when the commit is updated
|
||||
// +kubebuilder:validation:Optional
|
||||
UpdateTime metav1.Time `json:"updateTime"`
|
||||
|
||||
// CommitTime is the time when the commit is created
|
||||
// +kubebuilder:validation:Optional
|
||||
CommitTime metav1.Time `json:"commitTime"`
|
||||
|
||||
// CommitStatus is the status of the commit
|
||||
// +kubebuilder:validation:Enum=Success;Failed;Pending;Committing
|
||||
// +kubebuilder:default=Pending
|
||||
CommitStatus CommitStatus `json:"commitStatus"`
|
||||
}
|
||||
|
||||
// CommitRecordMap is a map of commit records, key is the commit id
|
||||
type CommitRecordMap map[string]*CommitRecord
|
||||
|
||||
// DevboxStatus defines the observed state of Devbox
|
||||
type DevboxStatus struct {
|
||||
// ObservedGeneration is the most recent generation observed by the controller.
|
||||
// It is updated when the controller has reconciled the spec for that generation.
|
||||
// +kubebuilder:validation:Optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
|
||||
// Conditions represent the latest available observations of the Devbox's state.
|
||||
// +kubebuilder:validation:Optional
|
||||
// +kubebuilder:default={}
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
|
||||
// +kubebuilder:validation:Optional
|
||||
ContentID string `json:"contentID"`
|
||||
// +kubebuilder:validation:Optional
|
||||
Node string `json:"node"`
|
||||
// +kubebuilder:validation:Optional
|
||||
// +kubebuilder:default=Running
|
||||
State DevboxState `json:"state"`
|
||||
// CommitRecords is the records of the devbox commits
|
||||
CommitRecords CommitRecordMap `json:"commitRecords"`
|
||||
// +kubebuilder:validation:Optional
|
||||
Phase DevboxPhase `json:"phase"`
|
||||
// +kubebuilder:validation:Optional
|
||||
Network NetworkStatus `json:"network,omitempty"`
|
||||
|
||||
// +kubebuilder:validation:Optional
|
||||
LastContainerStatus corev1.ContainerStatus `json:"lastContainerStatus"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:printcolumn:name="State",type="string",JSONPath=".spec.state"
|
||||
// +kubebuilder:printcolumn:name="NetworkType",type="string",JSONPath=".status.network.type"
|
||||
// +kubebuilder:printcolumn:name="UniqueID",type="string",JSONPath=".status.network.uniqueID"
|
||||
// +kubebuilder:printcolumn:name="NodePort",type="integer",JSONPath=".status.network.nodePort"
|
||||
// +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase"
|
||||
// +kubebuilder:printcolumn:name="Node",type="string",JSONPath=".status.node"
|
||||
|
||||
// Devbox is the Schema for the devboxes API
|
||||
type Devbox struct {
|
||||
// +kubebuilder:validation:XValidation:rule="self.spec.state == oldSelf.spec.state || (self.status.contentID in self.status.commitRecords && self.status.commitRecords[self.status.contentID].commitStatus != 'Committing')"
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec DevboxSpec `json:"spec,omitempty"`
|
||||
Status DevboxStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// DevboxList contains a list of Devbox
|
||||
type DevboxList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []Devbox `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&Devbox{}, &DevboxList{})
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1alpha2
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// DevBoxReleaseSpec defines the desired state of Devboxrelease.
|
||||
type DevBoxReleaseSpec struct {
|
||||
// +kubebuilder:validation:Required
|
||||
DevboxName string `json:"devboxName"`
|
||||
// +kubebuilder:validation:Required
|
||||
Version string `json:"version"`
|
||||
// +kubebuilder:validation:Optional
|
||||
Notes string `json:"notes,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
// +kubebuilder:default=false
|
||||
StartDevboxAfterRelease bool `json:"startDevboxAfterRelease,omitempty"`
|
||||
}
|
||||
|
||||
type DevBoxReleasePhase string
|
||||
|
||||
const (
|
||||
// DevBoxReleasePhaseSuccess means the Devbox has been released
|
||||
DevBoxReleasePhaseSuccess DevBoxReleasePhase = "Success"
|
||||
// DevBoxReleasePhasePending means the Devbox has not been released
|
||||
DevBoxReleasePhasePending DevBoxReleasePhase = "Pending"
|
||||
// DevBoxReleasePhaseFailed means the Devbox has not been released
|
||||
DevBoxReleasePhaseFailed DevBoxReleasePhase = "Failed"
|
||||
)
|
||||
|
||||
type DevBoxReleaseStatus struct {
|
||||
// +kubebuilder:validation:Optional
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Success;Pending;Failed
|
||||
Phase DevBoxReleasePhase `json:"phase,omitempty"`
|
||||
OriginalDevboxState DevboxState `json:"originalDevboxState,omitempty"`
|
||||
SourceImage string `json:"sourceImage,omitempty"`
|
||||
TargetImage string `json:"targetImage,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:storageversion
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase"
|
||||
// +kubebuilder:printcolumn:name="Version",type="string",JSONPath=".spec.version"
|
||||
// +kubebuilder:printcolumn:name="SourceImage",type="string",JSONPath=".status.sourceImage"
|
||||
// +kubebuilder:printcolumn:name="TargetImage",type="string",JSONPath=".status.targetImage"
|
||||
// +kubebuilder:printcolumn:name="StartDevboxAfterRelease",type="boolean",JSONPath=".spec.startDevboxAfterRelease"
|
||||
|
||||
// DevBoxRelease is the Schema for the devboxreleases API.
|
||||
type DevBoxRelease struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec DevBoxReleaseSpec `json:"spec,omitempty"`
|
||||
Status DevBoxReleaseStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// DevBoxReleaseList contains a list of DevBoxRelease.
|
||||
type DevBoxReleaseList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []DevBoxRelease `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&DevBoxRelease{}, &DevBoxReleaseList{})
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package v1alpha2 contains API Schema definitions for the devbox v1alpha2 API group
|
||||
// +kubebuilder:object:generate=true
|
||||
// +groupName=devbox.sealos.io
|
||||
package v1alpha2
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/scheme"
|
||||
)
|
||||
|
||||
var (
|
||||
// GroupVersion is group version used to register these objects
|
||||
GroupVersion = schema.GroupVersion{Group: "devbox.sealos.io", Version: "v1alpha2"}
|
||||
|
||||
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
|
||||
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
|
||||
|
||||
// AddToScheme adds the types in this group-version to the given scheme.
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
@@ -1,431 +0,0 @@
|
||||
//go:build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha2
|
||||
|
||||
import (
|
||||
"k8s.io/api/core/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CommitRecord) DeepCopyInto(out *CommitRecord) {
|
||||
*out = *in
|
||||
in.GenerateTime.DeepCopyInto(&out.GenerateTime)
|
||||
in.ScheduleTime.DeepCopyInto(&out.ScheduleTime)
|
||||
in.UpdateTime.DeepCopyInto(&out.UpdateTime)
|
||||
in.CommitTime.DeepCopyInto(&out.CommitTime)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommitRecord.
|
||||
func (in *CommitRecord) DeepCopy() *CommitRecord {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CommitRecord)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in CommitRecordMap) DeepCopyInto(out *CommitRecordMap) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(CommitRecordMap, len(*in))
|
||||
for key, val := range *in {
|
||||
var outVal *CommitRecord
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
inVal := (*in)[key]
|
||||
in, out := &inVal, &outVal
|
||||
*out = new(CommitRecord)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
(*out)[key] = outVal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommitRecordMap.
|
||||
func (in CommitRecordMap) DeepCopy() CommitRecordMap {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CommitRecordMap)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Config) DeepCopyInto(out *Config) {
|
||||
*out = *in
|
||||
if in.Labels != nil {
|
||||
in, out := &in.Labels, &out.Labels
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.Annotations != nil {
|
||||
in, out := &in.Annotations, &out.Annotations
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
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([]v1.EnvVar, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.ReleaseCommand != nil {
|
||||
in, out := &in.ReleaseCommand, &out.ReleaseCommand
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ReleaseArgs != nil {
|
||||
in, out := &in.ReleaseArgs, &out.ReleaseArgs
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Ports != nil {
|
||||
in, out := &in.Ports, &out.Ports
|
||||
*out = make([]v1.ContainerPort, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.AppPorts != nil {
|
||||
in, out := &in.AppPorts, &out.AppPorts
|
||||
*out = make([]v1.ServicePort, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.VolumeMounts != nil {
|
||||
in, out := &in.VolumeMounts, &out.VolumeMounts
|
||||
*out = make([]v1.VolumeMount, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Volumes != nil {
|
||||
in, out := &in.Volumes, &out.Volumes
|
||||
*out = make([]v1.Volume, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Config.
|
||||
func (in *Config) DeepCopy() *Config {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Config)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DevBoxRelease) DeepCopyInto(out *DevBoxRelease) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
out.Spec = in.Spec
|
||||
out.Status = in.Status
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DevBoxRelease.
|
||||
func (in *DevBoxRelease) DeepCopy() *DevBoxRelease {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DevBoxRelease)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *DevBoxRelease) 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 *DevBoxReleaseList) DeepCopyInto(out *DevBoxReleaseList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]DevBoxRelease, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DevBoxReleaseList.
|
||||
func (in *DevBoxReleaseList) DeepCopy() *DevBoxReleaseList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DevBoxReleaseList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *DevBoxReleaseList) 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 *DevBoxReleaseSpec) DeepCopyInto(out *DevBoxReleaseSpec) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DevBoxReleaseSpec.
|
||||
func (in *DevBoxReleaseSpec) DeepCopy() *DevBoxReleaseSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DevBoxReleaseSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DevBoxReleaseStatus) DeepCopyInto(out *DevBoxReleaseStatus) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DevBoxReleaseStatus.
|
||||
func (in *DevBoxReleaseStatus) DeepCopy() *DevBoxReleaseStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DevBoxReleaseStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Devbox) DeepCopyInto(out *Devbox) {
|
||||
*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 Devbox.
|
||||
func (in *Devbox) DeepCopy() *Devbox {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Devbox)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Devbox) 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 *DevboxList) DeepCopyInto(out *DevboxList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Devbox, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DevboxList.
|
||||
func (in *DevboxList) DeepCopy() *DevboxList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DevboxList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *DevboxList) 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 *DevboxSpec) DeepCopyInto(out *DevboxSpec) {
|
||||
*out = *in
|
||||
if in.Resource != nil {
|
||||
in, out := &in.Resource, &out.Resource
|
||||
*out = make(v1.ResourceList, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val.DeepCopy()
|
||||
}
|
||||
}
|
||||
in.Config.DeepCopyInto(&out.Config)
|
||||
in.NetworkSpec.DeepCopyInto(&out.NetworkSpec)
|
||||
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])
|
||||
}
|
||||
}
|
||||
if in.Affinity != nil {
|
||||
in, out := &in.Affinity, &out.Affinity
|
||||
*out = new(v1.Affinity)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DevboxSpec.
|
||||
func (in *DevboxSpec) DeepCopy() *DevboxSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DevboxSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DevboxStatus) DeepCopyInto(out *DevboxStatus) {
|
||||
*out = *in
|
||||
if in.CommitRecords != nil {
|
||||
in, out := &in.CommitRecords, &out.CommitRecords
|
||||
*out = make(CommitRecordMap, len(*in))
|
||||
for key, val := range *in {
|
||||
var outVal *CommitRecord
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
inVal := (*in)[key]
|
||||
in, out := &inVal, &outVal
|
||||
*out = new(CommitRecord)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
(*out)[key] = outVal
|
||||
}
|
||||
}
|
||||
out.Network = in.Network
|
||||
in.LastContainerStatus.DeepCopyInto(&out.LastContainerStatus)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DevboxStatus.
|
||||
func (in *DevboxStatus) DeepCopy() *DevboxStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DevboxStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NetworkSpec) DeepCopyInto(out *NetworkSpec) {
|
||||
*out = *in
|
||||
if in.ExtraPorts != nil {
|
||||
in, out := &in.ExtraPorts, &out.ExtraPorts
|
||||
*out = make([]v1.ContainerPort, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkSpec.
|
||||
func (in *NetworkSpec) DeepCopy() *NetworkSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NetworkSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NetworkStatus) DeepCopyInto(out *NetworkStatus) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkStatus.
|
||||
func (in *NetworkStatus) DeepCopy() *NetworkStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NetworkStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RuntimeRef) DeepCopyInto(out *RuntimeRef) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuntimeRef.
|
||||
func (in *RuntimeRef) DeepCopy() *RuntimeRef {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RuntimeRef)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
@@ -1,475 +0,0 @@
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
devboxv1alpha2 "github.com/labring/sealos/controllers/devbox/api/v1alpha2"
|
||||
"github.com/labring/sealos/controllers/devbox/internal/commit"
|
||||
"github.com/labring/sealos/controllers/devbox/internal/controller"
|
||||
"github.com/labring/sealos/controllers/devbox/internal/controller/utils/matcher"
|
||||
"github.com/labring/sealos/controllers/devbox/internal/controller/utils/nodes"
|
||||
"github.com/labring/sealos/controllers/devbox/internal/controller/utils/registry"
|
||||
utilresource "github.com/labring/sealos/controllers/devbox/internal/controller/utils/resource"
|
||||
"github.com/labring/sealos/controllers/devbox/internal/stat"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
// 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/client-go/rest"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/utils/ptr"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/cache"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
ctrlconfig "sigs.k8s.io/controller-runtime/pkg/config"
|
||||
"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"
|
||||
)
|
||||
|
||||
var (
|
||||
scheme = runtime.NewScheme()
|
||||
setupLog = ctrl.Log.WithName("setup")
|
||||
)
|
||||
|
||||
func init() {
|
||||
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
|
||||
utilruntime.Must(devboxv1alpha2.AddToScheme(scheme))
|
||||
// +kubebuilder:scaffold:scheme
|
||||
}
|
||||
|
||||
func main() {
|
||||
var metricsAddr string
|
||||
var probeAddr string
|
||||
var secureMetrics bool
|
||||
var enableHTTP2 bool
|
||||
var tlsOpts []func(*tls.Config)
|
||||
// debug flag
|
||||
var debugMode bool
|
||||
// registry flag
|
||||
var registryAddr string
|
||||
var registryUser string
|
||||
var registryPassword string
|
||||
// resource flag
|
||||
var requestCPURate float64
|
||||
var requestMemoryRate float64
|
||||
var requestEphemeralStorage string
|
||||
var limitEphemeralStorage string
|
||||
var maximumLimitEphemeralStorage string
|
||||
// pod matcher flag
|
||||
var enablePodResourceMatcher bool
|
||||
var enablePodEnvMatcher bool
|
||||
var enablePodPortMatcher bool
|
||||
var enablePodEphemeralStorageMatcher bool
|
||||
var enablePodStorageLimitMatcher bool
|
||||
// config qps and burst
|
||||
var configQPS int
|
||||
var configBurst int
|
||||
// config restart predicate duration
|
||||
var restartPredicateDuration time.Duration
|
||||
// devbox node label
|
||||
var devboxNodeLabel string
|
||||
var acceptanceThreshold int
|
||||
// merge base image layers flag
|
||||
var mergeBaseImageTopLayer bool
|
||||
// default base image flag for setLvRemovable's temp container
|
||||
var defaultBaseImage string
|
||||
// when this option is enabled, the controller will set up the block io resource configuration of a devbox pod
|
||||
var enableBlockIOResouce bool
|
||||
flag.StringVar(
|
||||
&defaultBaseImage,
|
||||
"default-base-image",
|
||||
"alpine:3.19",
|
||||
"The default base image for setLvRemovable's temp container",
|
||||
)
|
||||
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(
|
||||
&secureMetrics,
|
||||
"metrics-secure",
|
||||
true,
|
||||
"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.",
|
||||
)
|
||||
flag.BoolVar(&enableHTTP2, "enable-http2", false,
|
||||
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
|
||||
// debug flag
|
||||
flag.BoolVar(&debugMode, "debug", false, "If set, debug mode will be enabled")
|
||||
// registry flag
|
||||
flag.StringVar(®istryAddr, "registry-addr", "sealos.hub:5000", "The address of the registry")
|
||||
flag.StringVar(®istryUser, "registry-user", "admin", "The user of the registry")
|
||||
flag.StringVar(
|
||||
®istryPassword,
|
||||
"registry-password",
|
||||
"passw0rd",
|
||||
"The password of the registry",
|
||||
)
|
||||
// resource flag
|
||||
flag.Float64Var(
|
||||
&requestCPURate,
|
||||
"request-cpu-rate",
|
||||
10,
|
||||
"The request rate of cpu limit in devbox.",
|
||||
)
|
||||
flag.Float64Var(
|
||||
&requestMemoryRate,
|
||||
"request-memory-rate",
|
||||
10,
|
||||
"The request rate of memory limit in devbox.",
|
||||
)
|
||||
flag.StringVar(
|
||||
&requestEphemeralStorage,
|
||||
"request-ephemeral-storage",
|
||||
"500Mi",
|
||||
"The default request value of ephemeral storage in devbox.",
|
||||
)
|
||||
flag.StringVar(
|
||||
&limitEphemeralStorage,
|
||||
"limit-ephemeral-storage",
|
||||
"10Gi",
|
||||
"The default limit value of ephemeral storage in devbox.",
|
||||
)
|
||||
flag.StringVar(
|
||||
&maximumLimitEphemeralStorage,
|
||||
"maximum-limit-ephemeral-storage",
|
||||
"50Gi",
|
||||
"The maximum limit value of ephemeral storage in devbox.",
|
||||
)
|
||||
// pod matcher flag, pod resource matcher, env matcher, port matcher will be enabled by default, ephemeral storage matcher will be disabled by default
|
||||
flag.BoolVar(
|
||||
&enablePodResourceMatcher,
|
||||
"enable-pod-resource-matcher",
|
||||
true,
|
||||
"If set, pod resource matcher will be enabled",
|
||||
)
|
||||
flag.BoolVar(
|
||||
&enablePodEnvMatcher,
|
||||
"enable-pod-env-matcher",
|
||||
true,
|
||||
"If set, pod env matcher will be enabled",
|
||||
)
|
||||
flag.BoolVar(
|
||||
&enablePodPortMatcher,
|
||||
"enable-pod-port-matcher",
|
||||
true,
|
||||
"If set, pod port matcher will be enabled",
|
||||
)
|
||||
flag.BoolVar(
|
||||
&enablePodEphemeralStorageMatcher,
|
||||
"enable-pod-ephemeral-storage-matcher",
|
||||
false,
|
||||
"If set, pod ephemeral storage matcher will be enabled",
|
||||
)
|
||||
flag.BoolVar(
|
||||
&enablePodStorageLimitMatcher,
|
||||
"enable-pod-storage-limit-matcher",
|
||||
false,
|
||||
"If set, pod storage limit matcher will be enabled",
|
||||
)
|
||||
// config qps and burst
|
||||
flag.IntVar(&configQPS, "config-qps", 50, "The qps of the config")
|
||||
flag.IntVar(&configBurst, "config-burst", 100, "The burst of the config")
|
||||
// config restart predicate duration
|
||||
flag.DurationVar(
|
||||
&restartPredicateDuration,
|
||||
"restart-predicate-duration",
|
||||
10000*time.Hour,
|
||||
"Sets the restart predicate time duration for devbox controller restart. By default, the duration is set to 2 hours.",
|
||||
)
|
||||
// devbox node label
|
||||
flag.StringVar(
|
||||
&devboxNodeLabel,
|
||||
"devbox-node-label",
|
||||
"devbox.sealos.io/node",
|
||||
"The label of the devbox node",
|
||||
)
|
||||
// scheduling flags
|
||||
flag.IntVar(
|
||||
&acceptanceThreshold,
|
||||
"acceptance-threshold",
|
||||
16,
|
||||
"The minimum acceptance score for scheduling devbox to node. Default is 16, which means the node must have enough resources to run the devbox.",
|
||||
)
|
||||
// merge base image layers flag
|
||||
flag.BoolVar(
|
||||
&mergeBaseImageTopLayer,
|
||||
"merge-base-image-top-layer",
|
||||
false,
|
||||
"If set true, devbox will merge base image top layers during create and remove top layer during commit.",
|
||||
)
|
||||
flag.BoolVar(
|
||||
&enableBlockIOResouce,
|
||||
"enable-block-io-resource",
|
||||
false,
|
||||
"If this option is set to true, the controller will set up the block io resource configuration of a devbox pod",
|
||||
)
|
||||
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)
|
||||
}
|
||||
|
||||
webhookServer := webhook.NewServer(webhook.Options{
|
||||
TLSOpts: tlsOpts,
|
||||
})
|
||||
|
||||
// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.
|
||||
// More info:
|
||||
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.18.4/pkg/metrics/server
|
||||
// - https://book.kubebuilder.io/reference/metrics.html
|
||||
metricsServerOptions := metricsserver.Options{
|
||||
BindAddress: metricsAddr,
|
||||
SecureServing: secureMetrics,
|
||||
// TODO(user): TLSOpts is used to allow configuring the TLS config used for the server. If certificates are
|
||||
// not provided, self-signed certificates will be generated by default. This option is not recommended for
|
||||
// production environments as self-signed certificates do not offer the same level of trust and security
|
||||
// as certificates issued by a trusted Certificate Authority (CA). The primary risk is potentially allowing
|
||||
// unauthorized access to sensitive metrics data. Consider replacing with CertDir, CertName, and KeyName
|
||||
// to provide certificates, ensuring the server communicates using trusted and secure certificates.
|
||||
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/controller-runtime@v0.18.4/pkg/metrics/filters#WithAuthenticationAndAuthorization
|
||||
metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization
|
||||
}
|
||||
|
||||
cacheObjLabelSelector := labels.SelectorFromSet(map[string]string{
|
||||
"app.kubernetes.io/managed-by": "sealos",
|
||||
"app.kubernetes.io/part-of": "devbox",
|
||||
})
|
||||
|
||||
config := ctrl.GetConfigOrDie()
|
||||
// set qps and burst to config qps and burst for kube-config
|
||||
config.QPS = float32(configQPS)
|
||||
config.Burst = configBurst
|
||||
|
||||
mgr, err := ctrl.NewManager(config, ctrl.Options{
|
||||
Scheme: scheme,
|
||||
Metrics: metricsServerOptions,
|
||||
WebhookServer: webhookServer,
|
||||
HealthProbeBindAddress: probeAddr,
|
||||
LeaderElection: false,
|
||||
// LeaderElectionID: "b6694722.sealos.io",
|
||||
// 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,
|
||||
|
||||
NewCache: func(config *rest.Config, opts cache.Options) (cache.Cache, error) {
|
||||
opts.ByObject = map[client.Object]cache.ByObject{
|
||||
&corev1.Service{}: {Label: cacheObjLabelSelector},
|
||||
&corev1.Pod{}: {Label: cacheObjLabelSelector},
|
||||
&corev1.Secret{}: {Label: cacheObjLabelSelector},
|
||||
}
|
||||
// set sync period to 1 hour for devbox controller to reconcile all devboxes.
|
||||
opts.SyncPeriod = ptr.To(time.Hour)
|
||||
return cache.New(config, opts)
|
||||
},
|
||||
Controller: ctrlconfig.Controller{
|
||||
UsePriorityQueue: ptr.To(true),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to start manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
podMatchers := []matcher.PodMatcher{}
|
||||
if enablePodResourceMatcher {
|
||||
podMatchers = append(podMatchers, matcher.ResourceMatcher{})
|
||||
}
|
||||
if enablePodEnvMatcher {
|
||||
podMatchers = append(podMatchers, matcher.EnvVarMatcher{})
|
||||
}
|
||||
if enablePodPortMatcher {
|
||||
podMatchers = append(podMatchers, matcher.PortMatcher{})
|
||||
}
|
||||
if enablePodEphemeralStorageMatcher {
|
||||
podMatchers = append(podMatchers, matcher.EphemeralStorageMatcher{})
|
||||
}
|
||||
if enablePodStorageLimitMatcher {
|
||||
podMatchers = append(podMatchers, matcher.StorageLimitMatcher{})
|
||||
}
|
||||
|
||||
stateChangeBroadcaster := record.NewBroadcaster()
|
||||
|
||||
startupCMName := os.Getenv("DEVBOX_STARTUP_CM_NAME")
|
||||
startupCMNamespace := os.Getenv("DEVBOX_STARTUP_CM_NAMESPACE")
|
||||
if (startupCMName != "" && startupCMNamespace == "") ||
|
||||
(startupCMName == "" && startupCMNamespace != "") {
|
||||
setupLog.Error(
|
||||
nil,
|
||||
"both DEVBOX_STARTUP_CM_NAME and DEVBOX_STARTUP_CM_NAMESPACE must be set together",
|
||||
)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err = (&controller.DevboxReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
Recorder: mgr.GetEventRecorderFor("devbox-controller"),
|
||||
StateChangeRecorder: stateChangeBroadcaster.NewRecorder(
|
||||
mgr.GetScheme(),
|
||||
corev1.EventSource{Component: "devbox-controller", Host: nodes.GetNodeName()}),
|
||||
CommitImageRegistry: registryAddr,
|
||||
RequestRate: utilresource.RequestRate{
|
||||
CPU: requestCPURate,
|
||||
Memory: requestMemoryRate,
|
||||
},
|
||||
EphemeralStorage: utilresource.EphemeralStorage{
|
||||
DefaultRequest: resource.MustParse(requestEphemeralStorage),
|
||||
DefaultLimit: resource.MustParse(limitEphemeralStorage),
|
||||
MaximumLimit: resource.MustParse(maximumLimitEphemeralStorage),
|
||||
},
|
||||
PodMatchers: podMatchers,
|
||||
DebugMode: debugMode,
|
||||
EnableBlockIOResource: enableBlockIOResouce,
|
||||
StartupConfigMapName: startupCMName,
|
||||
StartupConfigMapNamespace: startupCMNamespace,
|
||||
RestartPredicateDuration: restartPredicateDuration,
|
||||
NodeName: nodes.GetNodeName(),
|
||||
AcceptanceThreshold: acceptanceThreshold,
|
||||
NodeStatsProvider: &stat.NodeStatsProviderImpl{},
|
||||
MergeBaseImageTopLayer: mergeBaseImageTopLayer,
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "Devbox")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
committer, err := commit.NewCommitter(
|
||||
registryAddr,
|
||||
registryUser,
|
||||
registryPassword,
|
||||
mergeBaseImageTopLayer,
|
||||
)
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to create committer")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// if err := committer.InitializeGC(context.Background()); err != nil {
|
||||
// setupLog.Error(err, "unable to initialize GC")
|
||||
// os.Exit(1)
|
||||
// }
|
||||
|
||||
stateChangeHandler := controller.EventHandler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
Recorder: mgr.GetEventRecorderFor("state-change-handler"),
|
||||
Committer: committer,
|
||||
CommitImageRegistry: registryAddr,
|
||||
NodeName: nodes.GetNodeName(),
|
||||
Logger: ctrl.Log.WithName("state-change-handler"),
|
||||
DefaultBaseImage: defaultBaseImage,
|
||||
}
|
||||
|
||||
setupLog.Info("StateChangeHandler initialized", "nodeName", nodes.GetNodeName())
|
||||
|
||||
watcher := stateChangeBroadcaster.StartEventWatcher(func(event *corev1.Event) {
|
||||
setupLog.Info("Event received by watcher",
|
||||
"event", event.Name,
|
||||
"eventSourceHost", event.Source.Host,
|
||||
"eventType", event.Type,
|
||||
"eventReason", event.Reason)
|
||||
if err := stateChangeHandler.Handle(context.TODO(), event); err != nil {
|
||||
setupLog.Error(err, "failed to handle event", "event", event.Name)
|
||||
}
|
||||
})
|
||||
defer watcher.Stop()
|
||||
|
||||
if err = (&controller.DevboxreleaseReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
Registry: registry.Registry{
|
||||
Host: registryAddr,
|
||||
BasicAuth: registry.BasicAuth{
|
||||
Username: registryUser,
|
||||
Password: registryPassword,
|
||||
},
|
||||
},
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "Devboxrelease")
|
||||
os.Exit(1)
|
||||
}
|
||||
// +kubebuilder:scaffold:builder
|
||||
|
||||
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
|
||||
setupLog.Error(err, "unable to set up health check")
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
|
||||
setupLog.Error(err, "unable to set up ready check")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
setupLog.Info("starting manager")
|
||||
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
|
||||
setupLog.Error(err, "problem running manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,91 +0,0 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.18.0
|
||||
name: devboxreleases.devbox.sealos.io
|
||||
spec:
|
||||
group: devbox.sealos.io
|
||||
names:
|
||||
kind: DevBoxRelease
|
||||
listKind: DevBoxReleaseList
|
||||
plural: devboxreleases
|
||||
singular: devboxrelease
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .status.phase
|
||||
name: Phase
|
||||
type: string
|
||||
- jsonPath: .spec.version
|
||||
name: Version
|
||||
type: string
|
||||
- jsonPath: .status.sourceImage
|
||||
name: SourceImage
|
||||
type: string
|
||||
- jsonPath: .status.targetImage
|
||||
name: TargetImage
|
||||
type: string
|
||||
- jsonPath: .spec.startDevboxAfterRelease
|
||||
name: StartDevboxAfterRelease
|
||||
type: boolean
|
||||
name: v1alpha2
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: DevBoxRelease is the Schema for the devboxreleases API.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: DevBoxReleaseSpec defines the desired state of Devboxrelease.
|
||||
properties:
|
||||
devboxName:
|
||||
type: string
|
||||
notes:
|
||||
type: string
|
||||
startDevboxAfterRelease:
|
||||
default: false
|
||||
type: boolean
|
||||
version:
|
||||
type: string
|
||||
required:
|
||||
- devboxName
|
||||
- version
|
||||
type: object
|
||||
status:
|
||||
properties:
|
||||
originalDevboxState:
|
||||
type: string
|
||||
phase:
|
||||
default: Pending
|
||||
enum:
|
||||
- Success
|
||||
- Pending
|
||||
- Failed
|
||||
type: string
|
||||
sourceImage:
|
||||
type: string
|
||||
targetImage:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
@@ -1,37 +0,0 @@
|
||||
# Copyright © 2024 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# 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/devbox.sealos.io_devboxes.yaml
|
||||
- bases/devbox.sealos.io_devboxreleases.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
|
||||
|
||||
# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.
|
||||
# patches here are for enabling the CA injection for each CRD
|
||||
#- path: patches/cainjection_in_devboxes.yaml
|
||||
# +kubebuilder:scaffold:crdkustomizecainjectionpatch
|
||||
|
||||
# [WEBHOOK] To enable webhook, uncomment the following section
|
||||
# the following config is for teaching kustomize how to do kustomization for CRDs.
|
||||
|
||||
#configurations:
|
||||
#- kustomizeconfig.yaml
|
||||
@@ -1,33 +0,0 @@
|
||||
# Copyright © 2024 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# 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
|
||||
|
||||
namespace:
|
||||
- kind: CustomResourceDefinition
|
||||
version: v1
|
||||
group: apiextensions.k8s.io
|
||||
path: spec/conversion/webhook/clientConfig/service/namespace
|
||||
create: false
|
||||
|
||||
varReference:
|
||||
- path: metadata/annotations
|
||||
@@ -1,160 +0,0 @@
|
||||
# Copyright © 2024 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Adds namespace to all resources.
|
||||
namespace: devbox-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: devbox-
|
||||
|
||||
# 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
|
||||
|
||||
# Uncomment the patches line if you enable Metrics, and/or are using webhooks and cert-manager
|
||||
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
|
||||
|
||||
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
|
||||
# crd/kustomization.yaml
|
||||
#- path: manager_webhook_patch.yaml
|
||||
|
||||
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'.
|
||||
# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks.
|
||||
# 'CERTMANAGER' needs to be enabled to use ca injection
|
||||
#- path: webhookcainjection_patch.yaml
|
||||
|
||||
# [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: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs
|
||||
# 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
|
||||
# - select:
|
||||
# kind: MutatingWebhookConfiguration
|
||||
# fieldPaths:
|
||||
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
|
||||
# options:
|
||||
# delimiter: '/'
|
||||
# index: 0
|
||||
# create: true
|
||||
# - select:
|
||||
# kind: CustomResourceDefinition
|
||||
# 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 # this name should match the one in certificate.yaml
|
||||
# fieldPath: .metadata.name
|
||||
# targets:
|
||||
# - select:
|
||||
# kind: ValidatingWebhookConfiguration
|
||||
# fieldPaths:
|
||||
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
|
||||
# options:
|
||||
# delimiter: '/'
|
||||
# index: 1
|
||||
# create: true
|
||||
# - select:
|
||||
# kind: MutatingWebhookConfiguration
|
||||
# fieldPaths:
|
||||
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
|
||||
# options:
|
||||
# delimiter: '/'
|
||||
# index: 1
|
||||
# create: true
|
||||
# - select:
|
||||
# kind: CustomResourceDefinition
|
||||
# fieldPaths:
|
||||
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
|
||||
# options:
|
||||
# delimiter: '/'
|
||||
# index: 1
|
||||
# create: true
|
||||
# - source: # Add cert-manager annotation to the webhook Service
|
||||
# kind: Service
|
||||
# version: v1
|
||||
# name: webhook-service
|
||||
# fieldPath: .metadata.name # namespace of the service
|
||||
# targets:
|
||||
# - select:
|
||||
# kind: Certificate
|
||||
# group: cert-manager.io
|
||||
# version: v1
|
||||
# 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
|
||||
# fieldPaths:
|
||||
# - .spec.dnsNames.0
|
||||
# - .spec.dnsNames.1
|
||||
# options:
|
||||
# delimiter: '.'
|
||||
# index: 1
|
||||
# create: true
|
||||
@@ -1,18 +0,0 @@
|
||||
# Copyright © 2024 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# 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
|
||||
@@ -1,31 +0,0 @@
|
||||
# Copyright © 2024 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
control-plane: controller-manager
|
||||
app.kubernetes.io/name: devbox
|
||||
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
|
||||
@@ -1,22 +0,0 @@
|
||||
# Copyright © 2024 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
resources:
|
||||
- manager.yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
images:
|
||||
- name: controller
|
||||
newName: ghcr.io/labring/sealos-devbox-controller
|
||||
newTag: latest
|
||||
@@ -1,112 +0,0 @@
|
||||
# Copyright © 2024 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
labels:
|
||||
control-plane: controller-manager
|
||||
app.kubernetes.io/name: devbox
|
||||
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: devbox
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
control-plane: controller-manager
|
||||
replicas: 2
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
kubectl.kubernetes.io/default-container: manager
|
||||
labels:
|
||||
control-plane: controller-manager
|
||||
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:
|
||||
runAsNonRoot: true
|
||||
# TODO(user): For common cases that do not require escalating privileges
|
||||
# it is recommended to ensure that all your Pods/Containers are restrictive.
|
||||
# More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted
|
||||
# Please uncomment the following code if your project does NOT have to work on old Kubernetes
|
||||
# versions < 1.19 or on vendors versions which do NOT support this field by default (i.e. Openshift < 4.11 ).
|
||||
# seccompProfile:
|
||||
# type: RuntimeDefault
|
||||
containers:
|
||||
- command:
|
||||
- /manager
|
||||
args:
|
||||
- --leader-elect
|
||||
- --health-probe-bind-address=:8081
|
||||
- --registry-addr={{ .registryAddr }}
|
||||
- --registry-user={{ .registryUser }}
|
||||
- --registry-password={{ .registryPassword }}
|
||||
image: controller:latest
|
||||
name: manager
|
||||
securityContext:
|
||||
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: 1500m
|
||||
memory: 2000Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 640Mi
|
||||
serviceAccountName: controller-manager
|
||||
terminationGracePeriodSeconds: 10
|
||||
@@ -1,16 +0,0 @@
|
||||
# Copyright © 2024 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
resources:
|
||||
- monitor.yaml
|
||||
@@ -1,44 +0,0 @@
|
||||
# Copyright © 2024 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Prometheus Monitor Service (Metrics)
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
labels:
|
||||
control-plane: controller-manager
|
||||
app.kubernetes.io/name: devbox
|
||||
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. This poses a significant security risk by making the system vulnerable to
|
||||
# man-in-the-middle attacks, where an attacker could intercept and manipulate the communication between
|
||||
# Prometheus and the monitored services. This could lead to unauthorized access to sensitive metrics data,
|
||||
# compromising the integrity and confidentiality of the information.
|
||||
# Please use the following options for secure configurations:
|
||||
# caFile: /etc/metrics-certs/ca.crt
|
||||
# certFile: /etc/metrics-certs/tls.crt
|
||||
# keyFile: /etc/metrics-certs/tls.key
|
||||
insecureSkipVerify: true
|
||||
selector:
|
||||
matchLabels:
|
||||
control-plane: controller-manager
|
||||
@@ -1,41 +0,0 @@
|
||||
# Copyright © 2024 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# permissions for end users to edit devboxes.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: devbox
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: devbox-editor-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- devbox.sealos.io
|
||||
resources:
|
||||
- devboxes
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- devbox.sealos.io
|
||||
resources:
|
||||
- devboxes/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -1,37 +0,0 @@
|
||||
# Copyright © 2024 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# permissions for end users to view devboxes.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: devbox
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: devbox-viewer-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- devbox.sealos.io
|
||||
resources:
|
||||
- devboxes
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- devbox.sealos.io
|
||||
resources:
|
||||
- devboxes/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -1,27 +0,0 @@
|
||||
# This rule is not used by the project devbox itself.
|
||||
# It is provided to allow the cluster admin to help manage permissions for users.
|
||||
#
|
||||
# Grants full permissions ('*') over devbox.sealos.io.
|
||||
# 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: devbox
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: devboxrelease-admin-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- devbox.sealos.io
|
||||
resources:
|
||||
- devboxreleases
|
||||
verbs:
|
||||
- '*'
|
||||
- apiGroups:
|
||||
- devbox.sealos.io
|
||||
resources:
|
||||
- devboxreleases/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -1,33 +0,0 @@
|
||||
# This rule is not used by the project devbox 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 devbox.sealos.io.
|
||||
# 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: devbox
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: devboxrelease-editor-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- devbox.sealos.io
|
||||
resources:
|
||||
- devboxreleases
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- devbox.sealos.io
|
||||
resources:
|
||||
- devboxreleases/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -1,29 +0,0 @@
|
||||
# This rule is not used by the project devbox itself.
|
||||
# It is provided to allow the cluster admin to help manage permissions for users.
|
||||
#
|
||||
# Grants read-only access to devbox.sealos.io 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: devbox
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: devboxrelease-viewer-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- devbox.sealos.io
|
||||
resources:
|
||||
- devboxreleases
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- devbox.sealos.io
|
||||
resources:
|
||||
- devboxreleases/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -1,47 +0,0 @@
|
||||
# Copyright © 2024 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
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, "Editor" and "Viewer" roles are scaffolded by
|
||||
# default, aiding admins in cluster management. Those roles are
|
||||
# not used by the Project itself. You can comment the following lines
|
||||
# if you do not want those helpers be installed with your Project.
|
||||
- devbox_editor_role.yaml
|
||||
- devbox_viewer_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 {{ .ProjectName }} itself. You can comment the following lines
|
||||
# if you do not want those helpers be installed with your Project.
|
||||
- devboxrelease_admin_role.yaml
|
||||
- devboxrelease_editor_role.yaml
|
||||
- devboxrelease_viewer_role.yaml
|
||||
@@ -1,54 +0,0 @@
|
||||
# Copyright © 2024 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# permissions to do leader election.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: devbox
|
||||
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
|
||||
@@ -1,29 +0,0 @@
|
||||
# Copyright © 2024 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: devbox
|
||||
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
|
||||
@@ -1,31 +0,0 @@
|
||||
# Copyright © 2024 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
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
|
||||
@@ -1,26 +0,0 @@
|
||||
# Copyright © 2024 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
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
|
||||
@@ -1,23 +0,0 @@
|
||||
# Copyright © 2024 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: metrics-reader
|
||||
rules:
|
||||
- nonResourceURLs:
|
||||
- "/metrics"
|
||||
verbs:
|
||||
- get
|
||||
@@ -1,69 +0,0 @@
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: manager-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps
|
||||
- events
|
||||
- pods
|
||||
- secrets
|
||||
- services
|
||||
verbs:
|
||||
- '*'
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- nodes
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- nodes/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- devbox.sealos.io
|
||||
resources:
|
||||
- devboxes
|
||||
- devboxreleases
|
||||
- runtimeclasses
|
||||
- runtimes
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- devbox.sealos.io
|
||||
resources:
|
||||
- devboxes/finalizers
|
||||
- devboxreleases/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- devbox.sealos.io
|
||||
resources:
|
||||
- devboxes/status
|
||||
- devboxreleases/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
@@ -1,29 +0,0 @@
|
||||
# Copyright © 2024 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: devbox
|
||||
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
|
||||
@@ -1,22 +0,0 @@
|
||||
# Copyright © 2024 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: devbox
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: controller-manager
|
||||
namespace: system
|
||||
@@ -1,23 +0,0 @@
|
||||
# Copyright © 2024 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: devbox.sealos.io/v1alpha2
|
||||
kind: OperationRequest
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: devbox
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: operationrequest-sample
|
||||
spec:
|
||||
# TODO(user): Add fields here
|
||||
@@ -1,11 +0,0 @@
|
||||
apiVersion: devbox.sealos.io/v1alpha2
|
||||
kind: Devboxrelease
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: devbox
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: devboxrelease-sample
|
||||
spec:
|
||||
devboxName: devbox-sample-1
|
||||
version: 1.0.0
|
||||
notes: "This is a sample devbox release"
|
||||
@@ -1,20 +0,0 @@
|
||||
# Copyright © 2024 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
## Append samples of your project ##
|
||||
resources:
|
||||
- devbox_v1alpha2_devbox.yaml
|
||||
- devbox_v1alpha2_devboxreleases.yaml
|
||||
- devbox_v1alpha2_devboxrelease.yaml
|
||||
# +kubebuilder:scaffold:manifestskustomizesamples
|
||||
@@ -1,13 +0,0 @@
|
||||
FROM scratch
|
||||
|
||||
USER 65532:65532
|
||||
|
||||
COPY registry registry
|
||||
COPY manifests manifests
|
||||
|
||||
ENV registryAddr="sealos.hub:5000"
|
||||
ENV registryUser=admin
|
||||
ENV registryPassword=passw0rd
|
||||
ENV authAddr="sealos.hub:5000"
|
||||
|
||||
CMD ["kubectl apply -f manifests"]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,37 +0,0 @@
|
||||
# Copyright © 2023 sealos.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: devbox-default-user
|
||||
namespace: devbox-system
|
||||
rules:
|
||||
- apiGroups: [ "devbox.sealos.io" ]
|
||||
resources: [ "runtimes", "runtimeclasses"]
|
||||
verbs: [ "get", "watch", "list" ]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: devbox-default-user-rolebinding
|
||||
namespace: devbox-system
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: devbox-default-user
|
||||
subjects:
|
||||
- kind: Group
|
||||
name: system:serviceaccounts
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
@@ -1,211 +0,0 @@
|
||||
module github.com/labring/sealos/controllers/devbox
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/containerd/containerd/v2 v2.1.4
|
||||
github.com/containerd/errdefs v1.0.0
|
||||
github.com/containerd/nerdctl/v2 v2.1.3
|
||||
github.com/go-logr/logr v1.4.3
|
||||
github.com/google/go-containerregistry v0.20.6
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/onsi/ginkgo/v2 v2.25.1
|
||||
github.com/onsi/gomega v1.38.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/crypto v0.42.0
|
||||
google.golang.org/grpc v1.73.0
|
||||
k8s.io/api v0.33.3
|
||||
k8s.io/apimachinery v0.33.3
|
||||
k8s.io/client-go v0.33.3
|
||||
k8s.io/cri-api v0.33.3
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738
|
||||
sigs.k8s.io/controller-runtime v0.20.4
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/containerd/nerdctl => github.com/labring/nerdctl/v2 v2.1.6-labring.1
|
||||
github.com/containerd/nerdctl/mod/tigron => github.com/labring/nerdctl/mod/tigron v0.0.0-20251118102352-14d1009e039d
|
||||
github.com/containerd/nerdctl/v2 => github.com/labring/nerdctl/v2 v2.1.6-labring.1
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.23.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/Microsoft/hcsshim v0.13.0 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cilium/ebpf v0.16.0 // indirect
|
||||
github.com/containerd/accelerated-container-image v1.3.0 // indirect
|
||||
github.com/containerd/cgroups/v3 v3.0.5 // indirect
|
||||
github.com/containerd/console v1.0.5 // indirect
|
||||
github.com/containerd/containerd/api v1.9.0 // indirect
|
||||
github.com/containerd/continuity v0.4.5 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/fifo v1.1.0 // indirect
|
||||
github.com/containerd/go-cni v1.1.13 // indirect
|
||||
github.com/containerd/go-runc v1.1.0 // indirect
|
||||
github.com/containerd/imgcrypt/v2 v2.0.1 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/nydus-snapshotter v0.15.4 // indirect
|
||||
github.com/containerd/platforms v1.0.0-rc.1 // indirect
|
||||
github.com/containerd/plugin v1.0.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter v0.17.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.17.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter/ipfs v0.17.0 // indirect
|
||||
github.com/containerd/ttrpc v1.2.7 // indirect
|
||||
github.com/containerd/typeurl/v2 v2.2.3 // indirect
|
||||
github.com/containernetworking/cni v1.3.0 // indirect
|
||||
github.com/containernetworking/plugins v1.8.0 // indirect
|
||||
github.com/containers/ocicrypt v1.2.1 // indirect
|
||||
github.com/coreos/go-iptables v0.8.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.6.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/djherbis/times v1.6.0 // indirect
|
||||
github.com/docker/cli v28.4.0+incompatible // indirect
|
||||
github.com/docker/docker v28.4.0+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.9.3 // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustinkirkland/golang-petname v0.0.0-20240428194347-eebcea082ee0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
|
||||
github.com/fahedouch/go-logrotate v0.3.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fluent/fluent-logger-golang v1.10.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-logr/zapr v1.3.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/cel-go v0.22.0 // indirect
|
||||
github.com/google/gnostic-models v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/ipfs/go-cid v0.5.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||
github.com/kpechenenko/rword v0.0.4 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/sys/mount v0.3.4 // indirect
|
||||
github.com/moby/sys/mountinfo v0.7.2 // indirect
|
||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||
github.com/moby/sys/signal v0.7.1 // indirect
|
||||
github.com/moby/sys/symlink v0.3.0 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
github.com/moby/term v0.5.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/multiformats/go-base32 v0.1.0 // indirect
|
||||
github.com/multiformats/go-base36 v0.2.0 // indirect
|
||||
github.com/multiformats/go-multiaddr v0.16.0 // indirect
|
||||
github.com/multiformats/go-multibase v0.2.0 // indirect
|
||||
github.com/multiformats/go-multihash v0.2.3 // indirect
|
||||
github.com/multiformats/go-varint v0.0.7 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.2.1 // indirect
|
||||
github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626 // indirect
|
||||
github.com/opencontainers/selinux v1.12.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.22.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.62.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/rootless-containers/bypass4netns v0.4.2 // indirect
|
||||
github.com/rootless-containers/rootlesskit/v2 v2.3.5 // indirect
|
||||
github.com/sasha-s/go-deadlock v0.3.5 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/smallstep/pkcs7 v0.1.1 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/spf13/cobra v1.10.1 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 // indirect
|
||||
github.com/stoewer/go-strcase v1.3.0 // indirect
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
|
||||
github.com/tinylib/msgp v1.3.0 // indirect
|
||||
github.com/tjarratt/babble v0.0.0-20210505082055-cbca2a4833c1 // indirect
|
||||
github.com/vbatts/tar-split v0.12.1 // indirect
|
||||
github.com/vishvananda/netlink v1.3.1 // indirect
|
||||
github.com/vishvananda/netns v0.0.5 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/yuchanns/srslog v1.1.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.36.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/net v0.44.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/term v0.35.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
|
||||
google.golang.org/protobuf v1.36.7 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.32.1 // indirect
|
||||
k8s.io/apiserver v0.32.3 // indirect
|
||||
k8s.io/component-base v0.32.3 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
|
||||
lukechampine.com/blake3 v1.3.0 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
tags.cncf.io/container-device-interface v1.0.1 // indirect
|
||||
tags.cncf.io/container-device-interface/specs-go v1.0.0 // indirect
|
||||
)
|
||||
@@ -1,652 +0,0 @@
|
||||
cel.dev/expr v0.23.0 h1:wUb94w6OYQS4uXraxo9U+wUAs9jT47Xvl4iPgAwM2ss=
|
||||
cel.dev/expr v0.23.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
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/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA=
|
||||
github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
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/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok=
|
||||
github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/compose-spec/compose-go/v2 v2.9.0 h1:UHSv/QHlo6QJtrT4igF1rdORgIUhDo1gWuyJUoiNNIM=
|
||||
github.com/compose-spec/compose-go/v2 v2.9.0/go.mod h1:Oky9AZGTRB4E+0VbTPZTUu4Kp+oEMMuwZXZtPPVT1iE=
|
||||
github.com/containerd/accelerated-container-image v1.3.0 h1:sFbTgSuMboeKHa9f7MY11hWF1XxVWjFoiTsXYtOtvdU=
|
||||
github.com/containerd/accelerated-container-image v1.3.0/go.mod h1:EvKVWor6ZQNUyYp0MZm5hw4k21ropuz7EegM+m/Jb/Q=
|
||||
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
|
||||
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
|
||||
github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc=
|
||||
github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/containerd/containerd/api v1.9.0 h1:HZ/licowTRazus+wt9fM6r/9BQO7S0vD5lMcWspGIg0=
|
||||
github.com/containerd/containerd/api v1.9.0/go.mod h1:GhghKFmTR3hNtyznBoQ0EMWr9ju5AqHjcZPsSpTKutI=
|
||||
github.com/containerd/containerd/v2 v2.1.4 h1:/hXWjiSFd6ftrBOBGfAZ6T30LJcx1dBjdKEeI8xucKQ=
|
||||
github.com/containerd/containerd/v2 v2.1.4/go.mod h1:8C5QV9djwsYDNhxfTCFjWtTBZrqjditQ4/ghHSYjnHM=
|
||||
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
|
||||
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
|
||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY=
|
||||
github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o=
|
||||
github.com/containerd/go-cni v1.1.13 h1:eFSGOKlhoYNxpJ51KRIMHZNlg5UgocXEIEBGkY7Hnis=
|
||||
github.com/containerd/go-cni v1.1.13/go.mod h1:nTieub0XDRmvCZ9VI/SBG6PyqT95N4FIhxsauF1vSBI=
|
||||
github.com/containerd/go-runc v1.1.0 h1:OX4f+/i2y5sUT7LhmcJH7GYrjjhHa1QI4e8yO0gGleA=
|
||||
github.com/containerd/go-runc v1.1.0/go.mod h1:xJv2hFF7GvHtTJd9JqTS2UVxMkULUYw4JN5XAUZqH5U=
|
||||
github.com/containerd/imgcrypt/v2 v2.0.1 h1:gQcmeCKA97fAl0wlpq0itSY/PagFBsn4/mlKUy6kOio=
|
||||
github.com/containerd/imgcrypt/v2 v2.0.1/go.mod h1:/qIJL8nxzdzMA2n5iYyyuIY36KfoVQWmgTWdfVtyebM=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/nydus-snapshotter v0.15.4 h1:l59kGRVMtwMLDLh322HsWhEsBCkRKMkGWYV5vBeLYCE=
|
||||
github.com/containerd/nydus-snapshotter v0.15.4/go.mod h1:eRJqnxQDr48HNop15kZdLZpFF5B6vf6Q11Aq1K0E4Ms=
|
||||
github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E=
|
||||
github.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4=
|
||||
github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y=
|
||||
github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8=
|
||||
github.com/containerd/stargz-snapshotter v0.17.0 h1:djNS4KU8ztFhLdEDZ1bsfzOiYuVHT6TgSU5qwRk+cNc=
|
||||
github.com/containerd/stargz-snapshotter v0.17.0/go.mod h1:ySEul1ck7jCE4jqsuFCo8FFLrHU20UWQeI9g7mdsanI=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.17.0 h1:+TyQIsR/zSFI1Rm31EQBwpAA1ovYgIKHy7kctL3sLcE=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.17.0/go.mod h1:s06tWAiJcXQo9/8AReBCIo/QxcXFZ2n4qfsRnpl71SM=
|
||||
github.com/containerd/stargz-snapshotter/ipfs v0.17.0 h1:Q7UO2U0nKXtQFYVeX8WUmMKXtJR9ZAPgISt/sMEo8Ng=
|
||||
github.com/containerd/stargz-snapshotter/ipfs v0.17.0/go.mod h1:zRJECfc6IPSr50ljYX36kVmrSd1Wdi3aXLzZhFuhfR4=
|
||||
github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ=
|
||||
github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o=
|
||||
github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40=
|
||||
github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk=
|
||||
github.com/containernetworking/cni v1.3.0 h1:v6EpN8RznAZj9765HhXQrtXgX+ECGebEYEmnuFjskwo=
|
||||
github.com/containernetworking/cni v1.3.0/go.mod h1:Bs8glZjjFfGPHMw6hQu82RUgEPNGEaBb9KS5KtNMnJ4=
|
||||
github.com/containernetworking/plugins v1.8.0 h1:WjGbV/0UQyo8A4qBsAh6GaDAtu1hevxVxsEuqtBqUFk=
|
||||
github.com/containernetworking/plugins v1.8.0/go.mod h1:JG3BxoJifxxHBhG3hFyxyhid7JgRVBu/wtooGEvWf1c=
|
||||
github.com/containers/ocicrypt v1.2.1 h1:0qIOTT9DoYwcKmxSt8QJt+VzMY18onl9jUXsxpVhSmM=
|
||||
github.com/containers/ocicrypt v1.2.1/go.mod h1:aD0AAqfMp0MtwqWgHM1bUwe1anx0VazI108CRrSKINQ=
|
||||
github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=
|
||||
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo=
|
||||
github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||
github.com/docker/cli v28.4.0+incompatible h1:RBcf3Kjw2pMtwui5V0DIMdyeab8glEw5QY0UUU4C9kY=
|
||||
github.com/docker/cli v28.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk=
|
||||
github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
|
||||
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustinkirkland/golang-petname v0.0.0-20240428194347-eebcea082ee0 h1:aYo8nnk3ojoQkP5iErif5Xxv0Mo0Ga/FR5+ffl/7+Nk=
|
||||
github.com/dustinkirkland/golang-petname v0.0.0-20240428194347-eebcea082ee0/go.mod h1:8AuBTZBRSFqEYBPYULd+NN474/zZBLP+6WeT5S9xlAc=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
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/fahedouch/go-logrotate v0.3.0 h1:XP+dHIDgWZ1ckz43mG6gl5ASer3PZDVr755SVMyzaUQ=
|
||||
github.com/fahedouch/go-logrotate v0.3.0/go.mod h1:X49m0bvPLkk71MHNCQ1yEfVEw8W/u+qvHa/hOnhCYf4=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fluent/fluent-logger-golang v1.10.1 h1:wu54iN1O2afll5oQrtTjhgZRwWcfOeFFzwRsEkABfFQ=
|
||||
github.com/fluent/fluent-logger-golang v1.10.1/go.mod h1:qOuXG4ZMrXaSTk12ua+uAb21xfNYOzn0roAtp7mfGAE=
|
||||
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.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
|
||||
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 v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
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/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/cel-go v0.22.0 h1:b3FJZxpiv1vTMo2/5RDUqAHPxkT8mmMfJIrq1llbf7g=
|
||||
github.com/google/cel-go v0.22.0/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8=
|
||||
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
|
||||
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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/go-containerregistry v0.20.6 h1:cvWX87UxxLgaH76b4hIvya6Dzz9qHB31qAwjAohdSTU=
|
||||
github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2FAkwCDf9/HZgsFJ02E2Y=
|
||||
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-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY=
|
||||
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
|
||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg=
|
||||
github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM=
|
||||
github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/kpechenenko/rword v0.0.4 h1:kwPwwx+gkGemHigIk1conGDijHWJs+lErl9g3KJQVMg=
|
||||
github.com/kpechenenko/rword v0.0.4/go.mod h1:xOh1FRoUbuuaUR25Bljbza63hhpqu8lvqAJ3NVdlm6M=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/labring/nerdctl/mod/tigron v0.0.0-20251118102352-14d1009e039d h1:6A0+TJfRXARP5LpjgE4ZCIgnW3aQV2hOao5a43BVEzY=
|
||||
github.com/labring/nerdctl/mod/tigron v0.0.0-20251118102352-14d1009e039d/go.mod h1:gmUZh2wUVxr/msGogKUi6v9eJbP5ASO4fVYEPzHH4iI=
|
||||
github.com/labring/nerdctl/v2 v2.1.6-labring.1 h1:k1MqC4uv7ZOmvUv3/IL6ShEEUUpS+kdGe8zd4JXRHlM=
|
||||
github.com/labring/nerdctl/v2 v2.1.6-labring.1/go.mod h1:XBIa8yzX2BddwiJtN47w4w4Xmki/Y6w8HHd5DfkjzlA=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
||||
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
|
||||
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
||||
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mndrix/tap-go v0.0.0-20171203230836-629fa407e90b/go.mod h1:pzzDgJWZ34fGzaAZGFW22KVZDfyrYW+QABMrWnJBnSs=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
|
||||
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
||||
github.com/moby/sys/mount v0.3.4 h1:yn5jq4STPztkkzSKpZkLcmjue+bZJ0u2AuQY1iNI1Ww=
|
||||
github.com/moby/sys/mount v0.3.4/go.mod h1:KcQJMbQdJHPlq5lcYT+/CjatWM4PuxKe+XLSVS4J6Os=
|
||||
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
|
||||
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
|
||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||
github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0=
|
||||
github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8=
|
||||
github.com/moby/sys/symlink v0.3.0 h1:GZX89mEZ9u53f97npBy4Rc3vJKj7JBDj/PN2I22GrNU=
|
||||
github.com/moby/sys/symlink v0.3.0/go.mod h1:3eNdhduHmYPcgsJtZXW1W4XUJdZGBIkttZ8xKqPUJq0=
|
||||
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
||||
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
|
||||
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
|
||||
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
|
||||
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
|
||||
github.com/multiformats/go-multiaddr v0.16.0 h1:oGWEVKioVQcdIOBlYM8BH1rZDWOGJSqr9/BKl6zQ4qc=
|
||||
github.com/multiformats/go-multiaddr v0.16.0/go.mod h1:JSVUmXDjsVFiW7RjIFMP7+Ev+h1DTbiJgVeTV/tcmP0=
|
||||
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
|
||||
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
|
||||
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
|
||||
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
|
||||
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
|
||||
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
|
||||
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.25.1 h1:Fwp6crTREKM+oA6Cz4MsO8RhKQzs2/gOIVOUscMAfZY=
|
||||
github.com/onsi/ginkgo/v2 v2.25.1/go.mod h1:ppTWQ1dh9KM/F1XgpeRqelR+zHVwV81DGRSDnFxK7Sk=
|
||||
github.com/onsi/gomega v1.38.1 h1:FaLA8GlcpXDwsb7m0h2A9ew2aTk3vnZMlzFgg5tz/pk=
|
||||
github.com/onsi/gomega v1.38.1/go.mod h1:LfcV8wZLvwcYRwPiJysphKAEsmcFnLMK/9c+PjvlX8g=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20220825212826-86290f6a00fb/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww=
|
||||
github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626 h1:DmNGcqH3WDbV5k8OJ+esPWbqUOX5rMLR2PMvziDMJi0=
|
||||
github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626/go.mod h1:BRHJJd0E+cx42OybVYSgUvZmU0B8P9gZuRXlZUP7TKI=
|
||||
github.com/opencontainers/selinux v1.9.1/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
|
||||
github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8=
|
||||
github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw=
|
||||
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
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/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rootless-containers/bypass4netns v0.4.2 h1:JUZcpX7VLRfDkLxBPC6fyNalJGv9MjnjECOilZIvKRc=
|
||||
github.com/rootless-containers/bypass4netns v0.4.2/go.mod h1:iOY28IeFVqFHnK0qkBCQ3eKzKQgSW5DtlXFQJyJMAQk=
|
||||
github.com/rootless-containers/rootlesskit/v2 v2.3.5 h1:WGY05oHE7xQpSkCGfYP9lMY5z19tCxA8PhWlvP1cKx8=
|
||||
github.com/rootless-containers/rootlesskit/v2 v2.3.5/go.mod h1:83EIYLeMX8UeNgLHkR1PefoSV76aKEC+OyI3vzrEfvw=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||
github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU=
|
||||
github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/smallstep/pkcs7 v0.1.1 h1:x+rPdt2W088V9Vkjho4KtoggyktZJlMduZAtRHm68LU=
|
||||
github.com/smallstep/pkcs7 v0.1.1/go.mod h1:dL6j5AIz9GHjVEBTXtW+QliALcgM19RtXaTeyxI+AfA=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||
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/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 h1:pnnLyeX7o/5aX8qUQ69P/mLojDqwda8hFOCBTmP/6hw=
|
||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6/go.mod h1:39R/xuhNgVhi+K0/zst4TLrJrVmbm6LVgl4A0+ZFS5M=
|
||||
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
|
||||
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
|
||||
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
||||
github.com/tjarratt/babble v0.0.0-20210505082055-cbca2a4833c1 h1:j8whCiEmvLCXI3scVn+YnklCU8mwJ9ZJ4/DGAKqQbRE=
|
||||
github.com/tjarratt/babble v0.0.0-20210505082055-cbca2a4833c1/go.mod h1:O5hBrCGqzfb+8WyY8ico2AyQau7XQwAfEQeEQ5/5V9E=
|
||||
github.com/urfave/cli v1.19.1/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
|
||||
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
|
||||
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
||||
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
|
||||
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
|
||||
github.com/yuchanns/srslog v1.1.0 h1:CEm97Xxxd8XpJThE0gc/XsqUGgPufh5u5MUjC27/KOk=
|
||||
github.com/yuchanns/srslog v1.1.0/go.mod h1:HsLjdv3XV02C3kgBW2bTyW6i88OQE+VYJZIxrPKPPak=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
||||
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
|
||||
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
||||
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
||||
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
|
||||
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
|
||||
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
||||
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
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/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
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/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
|
||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
||||
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
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.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.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.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8=
|
||||
k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE=
|
||||
k8s.io/apiextensions-apiserver v0.32.1 h1:hjkALhRUeCariC8DiVmb5jj0VjIc1N0DREP32+6UXZw=
|
||||
k8s.io/apiextensions-apiserver v0.32.1/go.mod h1:sxWIGuGiYov7Io1fAS2X06NjMIk5CbRHc2StSmbaQto=
|
||||
k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA=
|
||||
k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||
k8s.io/apiserver v0.32.3 h1:kOw2KBuHOA+wetX1MkmrxgBr648ksz653j26ESuWNY8=
|
||||
k8s.io/apiserver v0.32.3/go.mod h1:q1x9B8E/WzShF49wh3ADOh6muSfpmFL0I2t+TG0Zdgc=
|
||||
k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA=
|
||||
k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg=
|
||||
k8s.io/component-base v0.32.3 h1:98WJvvMs3QZ2LYHBzvltFSeJjEx7t5+8s71P7M74u8k=
|
||||
k8s.io/component-base v0.32.3/go.mod h1:LWi9cR+yPAv7cu2X9rZanTiFKB2kHA+JjmhkKjCZRpI=
|
||||
k8s.io/cri-api v0.33.3 h1:aQvK3UxsaVMul4z71lOiblMHdhw9ROaw3Cgg15xDrD4=
|
||||
k8s.io/cri-api v0.33.3/go.mod h1:OLQvT45OpIA+tv91ZrpuFIGY+Y2Ho23poS7n115Aocs=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
||||
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 h1:CPT0ExVicCzcpeN4baWEV2ko2Z/AsiZgEdwgcfwLgMo=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
|
||||
sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU=
|
||||
sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY=
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
|
||||
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
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/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
tags.cncf.io/container-device-interface v1.0.1 h1:KqQDr4vIlxwfYh0Ed/uJGVgX+CHAkahrgabg6Q8GYxc=
|
||||
tags.cncf.io/container-device-interface v1.0.1/go.mod h1:JojJIOeW3hNbcnOH2q0NrWNha/JuHoDZcmYxAZwb2i0=
|
||||
tags.cncf.io/container-device-interface/specs-go v1.0.0 h1:8gLw29hH1ZQP9K1YtAzpvkHCjjyIxHZYzBAvlQ+0vD8=
|
||||
tags.cncf.io/container-device-interface/specs-go v1.0.0/go.mod h1:u86hoFWqnh3hWz3esofRFKbI261bUlvUfLKGrDhJkgQ=
|
||||
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
@@ -1,658 +0,0 @@
|
||||
package commit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
containerd "github.com/containerd/containerd/v2/client"
|
||||
"github.com/containerd/containerd/v2/core/remotes"
|
||||
"github.com/containerd/containerd/v2/core/remotes/docker"
|
||||
"github.com/containerd/containerd/v2/core/remotes/docker/config"
|
||||
"github.com/containerd/containerd/v2/core/snapshots"
|
||||
"github.com/containerd/containerd/v2/pkg/namespaces"
|
||||
"github.com/containerd/nerdctl/v2/pkg/api/types"
|
||||
"github.com/containerd/nerdctl/v2/pkg/cmd/container"
|
||||
"github.com/containerd/nerdctl/v2/pkg/cmd/image"
|
||||
"github.com/containerd/nerdctl/v2/pkg/cmd/login"
|
||||
"github.com/containerd/nerdctl/v2/pkg/containerutil"
|
||||
ncdefaults "github.com/containerd/nerdctl/v2/pkg/defaults"
|
||||
"github.com/labring/sealos/controllers/devbox/api/v1alpha2"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
type Committer interface {
|
||||
CreateContainer(ctx context.Context, devboxName, contentID, baseImage string) (string, error)
|
||||
Commit(
|
||||
ctx context.Context,
|
||||
devboxName, contentID, baseImage, commitImage string,
|
||||
) (string, error)
|
||||
Push(ctx context.Context, imageName string) error
|
||||
RemoveImages(ctx context.Context, imageNames []string, force, async bool) error
|
||||
RemoveContainers(ctx context.Context, containerNames []string) error
|
||||
InitializeGC(ctx context.Context) error
|
||||
SetLvRemovable(ctx context.Context, containerID, contentID string) error
|
||||
}
|
||||
|
||||
type CommitterImpl struct {
|
||||
containerdClient *containerd.Client // containerd client
|
||||
conn *grpc.ClientConn // gRPC connection
|
||||
globalOptions *types.GlobalCommandOptions // global options
|
||||
registryAddr string
|
||||
registryUsername string
|
||||
registryPassword string
|
||||
// Merge base image layers control
|
||||
mergeBaseImageTopLayer bool
|
||||
}
|
||||
|
||||
// NewCommitter new a CommitterImpl with registry configuration
|
||||
func NewCommitter(
|
||||
registryAddr, registryUsername, registryPassword string,
|
||||
merge bool,
|
||||
) (Committer, error) {
|
||||
var conn *grpc.ClientConn
|
||||
var err error
|
||||
|
||||
// login to registry
|
||||
err = login.Login(context.Background(), types.LoginCommandOptions{
|
||||
GOptions: *NewGlobalOptionConfig(),
|
||||
ServerAddress: registryAddr,
|
||||
Username: registryUsername,
|
||||
Password: registryPassword,
|
||||
}, io.Discard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// retry to connect
|
||||
for i := 0; i <= DefaultMaxRetries; i++ {
|
||||
if i > 0 {
|
||||
log.Printf("Retrying connection to containerd (attempt %d/%d)...", i, DefaultMaxRetries)
|
||||
time.Sleep(DefaultRetryDelay)
|
||||
}
|
||||
|
||||
// create gRPC connection
|
||||
conn, err = grpc.NewClient(
|
||||
DefaultContainerdAddress,
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
)
|
||||
if err == nil {
|
||||
log.Printf("Successfully connected to containerd at %s", DefaultContainerdAddress)
|
||||
break
|
||||
}
|
||||
|
||||
log.Printf(
|
||||
"Failed to connect to containerd (attempt %d/%d): %v",
|
||||
i+1,
|
||||
DefaultMaxRetries+1,
|
||||
err,
|
||||
)
|
||||
|
||||
if i == DefaultMaxRetries {
|
||||
return nil, fmt.Errorf(
|
||||
"failed to connect to containerd after %d attempts: %w",
|
||||
DefaultMaxRetries+1,
|
||||
err,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// create Containerd client
|
||||
containerdClient, err := containerd.NewWithConn(
|
||||
conn,
|
||||
containerd.WithDefaultNamespace(DefaultNamespace),
|
||||
)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("failed to create containerd client: %w", err)
|
||||
}
|
||||
|
||||
return &CommitterImpl{
|
||||
containerdClient: containerdClient,
|
||||
conn: conn,
|
||||
globalOptions: NewGlobalOptionConfig(),
|
||||
registryAddr: registryAddr,
|
||||
registryUsername: registryUsername,
|
||||
registryPassword: registryPassword,
|
||||
mergeBaseImageTopLayer: merge,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateContainer create container with labels
|
||||
func (c *CommitterImpl) CreateContainer(
|
||||
ctx context.Context,
|
||||
devboxName, contentID, baseImage string,
|
||||
) (string, error) {
|
||||
log.Printf(
|
||||
"========>>>> create container, devboxName: %s, contentID: %s, baseImage: %s",
|
||||
devboxName,
|
||||
contentID,
|
||||
baseImage,
|
||||
)
|
||||
ctx = namespaces.WithNamespace(ctx, DefaultNamespace)
|
||||
|
||||
// check connection status, if connection is bad, try to reconnect
|
||||
if err := c.CheckConnection(ctx); err != nil {
|
||||
log.Printf("Connection check failed: %v, attempting to reconnect...", err)
|
||||
if reconnectErr := c.Reconnect(ctx); reconnectErr != nil {
|
||||
return "", fmt.Errorf("failed to reconnect: %w", reconnectErr)
|
||||
}
|
||||
}
|
||||
|
||||
// create container with labels
|
||||
originalAnnotations := map[string]string{
|
||||
v1alpha2.AnnotationContentID: contentID,
|
||||
v1alpha2.AnnotationStorageLimit: AnnotationUseLimitValue,
|
||||
AnnotationKeyNamespace: DefaultNamespace,
|
||||
AnnotationKeyImageName: baseImage,
|
||||
}
|
||||
|
||||
// Add merge base image layers annotation if enabled
|
||||
if c.mergeBaseImageTopLayer {
|
||||
originalAnnotations[v1alpha2.AnnotationInit] = AnnotationImageFromValue
|
||||
}
|
||||
|
||||
// convert labels to "containerd.io/snapshot/devbox-" format
|
||||
convertedLabels := convertLabels(originalAnnotations)
|
||||
convertedAnnotations := convertMapToSlice(originalAnnotations)
|
||||
|
||||
// create container options
|
||||
createOpt := types.ContainerCreateOptions{
|
||||
GOptions: *c.globalOptions,
|
||||
Runtime: DefaultRuntime, // user devbox runtime
|
||||
Name: fmt.Sprintf("devbox-%s-container-%d", devboxName, time.Now().UnixMicro()),
|
||||
Pull: "missing",
|
||||
InRun: false, // not start container
|
||||
Rm: false,
|
||||
LogDriver: "json-file",
|
||||
StopSignal: "SIGTERM",
|
||||
Restart: "unless-stopped",
|
||||
Interactive: false, // not interactive, avoid conflict with Detach
|
||||
Cgroupns: "host", // add cgroupns mode
|
||||
Detach: true, // run in background
|
||||
Rootfs: false,
|
||||
Label: convertedAnnotations,
|
||||
SnapshotLabels: convertedLabels,
|
||||
ImagePullOpt: types.ImagePullOptions{
|
||||
GOptions: *c.globalOptions,
|
||||
},
|
||||
}
|
||||
|
||||
// create network manager
|
||||
networkManager, err := containerutil.NewNetworkingOptionsManager(createOpt.GOptions,
|
||||
types.NetworkOptions{
|
||||
NetworkSlice: []string{DefaultNetworkMode},
|
||||
}, c.containerdClient)
|
||||
if err != nil {
|
||||
log.Println("failed to create network manager:", err)
|
||||
return "", fmt.Errorf("failed to create network manager: %w", err)
|
||||
}
|
||||
|
||||
// create container
|
||||
container, cleanup, err := container.Create(
|
||||
ctx,
|
||||
c.containerdClient,
|
||||
[]string{originalAnnotations[AnnotationKeyImageName]},
|
||||
networkManager,
|
||||
createOpt,
|
||||
)
|
||||
if err != nil {
|
||||
log.Println("failed to create container:", err)
|
||||
return "", fmt.Errorf("failed to create container: %w", err)
|
||||
}
|
||||
if cleanup != nil {
|
||||
defer cleanup()
|
||||
}
|
||||
|
||||
log.Printf("container created successfully: %s\n", container.ID())
|
||||
return container.ID(), nil
|
||||
}
|
||||
|
||||
// DeleteContainer delete container
|
||||
func (c *CommitterImpl) DeleteContainer(ctx context.Context, containerName string) error {
|
||||
ctx = namespaces.WithNamespace(ctx, DefaultNamespace)
|
||||
container, err := c.containerdClient.LoadContainer(ctx, containerName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load container: %w", err)
|
||||
}
|
||||
|
||||
// try to get and stop task
|
||||
task, err := container.Task(ctx, nil)
|
||||
if err == nil {
|
||||
log.Printf("Stopping task for container: %s", containerName)
|
||||
|
||||
// force kill task
|
||||
err = task.Kill(ctx, 9) // SIGKILL
|
||||
if err != nil {
|
||||
log.Printf("Warning: failed to send SIGKILL: %v", err)
|
||||
} else {
|
||||
log.Printf("Sent SIGKILL to task")
|
||||
}
|
||||
|
||||
// delete task
|
||||
log.Printf("Deleting task...")
|
||||
_, err = task.Delete(ctx, containerd.WithProcessKill)
|
||||
if err != nil {
|
||||
log.Printf("Warning: failed to delete task: %v", err)
|
||||
} else {
|
||||
log.Printf("Task deleted for container: %s", containerName)
|
||||
}
|
||||
}
|
||||
|
||||
// delete container (include snapshot)
|
||||
err = container.Delete(ctx, containerd.WithSnapshotCleanup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete container: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("Container deleted: %s successfully", containerName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CommitterImpl) SetLvRemovable(ctx context.Context, containerID, contentID string) error {
|
||||
log.Printf(
|
||||
"========>>>> set lv removable for container, containerID: %s, contentID: %s",
|
||||
containerID,
|
||||
contentID,
|
||||
)
|
||||
ctx = namespaces.WithNamespace(ctx, DefaultNamespace)
|
||||
|
||||
// check connection status, if connection is bad, try to reconnect
|
||||
if err := c.CheckConnection(ctx); err != nil {
|
||||
log.Printf("Connection check failed: %v, attempting to reconnect...", err)
|
||||
if reconnectErr := c.Reconnect(ctx); reconnectErr != nil {
|
||||
return fmt.Errorf("failed to reconnect: %w", reconnectErr)
|
||||
}
|
||||
}
|
||||
|
||||
_, err := c.containerdClient.SnapshotService(DefaultDevboxSnapshotter).
|
||||
Update(ctx, snapshots.Info{
|
||||
Name: containerID,
|
||||
Labels: map[string]string{RemoveContentIDkey: contentID},
|
||||
}, "labels."+RemoveContentIDkey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveContainer remove container
|
||||
func (c *CommitterImpl) RemoveContainers(ctx context.Context, containerNames []string) error {
|
||||
if len(containerNames) == 0 {
|
||||
return errors.New("[RemoveContainers]containerNames is empty")
|
||||
}
|
||||
|
||||
log.Printf("========>>>> remove container, containerNames: %v", containerNames)
|
||||
ctx = namespaces.WithNamespace(ctx, DefaultNamespace)
|
||||
|
||||
// check connection status, if connection is bad, try to reconnect
|
||||
if err := c.CheckConnection(ctx); err != nil {
|
||||
log.Printf("Connection check failed: %v, attempting to reconnect...", err)
|
||||
if reconnectErr := c.Reconnect(ctx); reconnectErr != nil {
|
||||
return fmt.Errorf("failed to reconnect: %w", reconnectErr)
|
||||
}
|
||||
}
|
||||
|
||||
global := NewGlobalOptionConfig()
|
||||
opt := types.ContainerRemoveOptions{
|
||||
Stdout: io.Discard,
|
||||
Force: DefaultRemoveContainerForce,
|
||||
Volumes: false,
|
||||
GOptions: *global,
|
||||
}
|
||||
err := container.Remove(ctx, c.containerdClient, containerNames, opt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove container: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Commit commit container to image
|
||||
func (c *CommitterImpl) Commit(
|
||||
ctx context.Context,
|
||||
devboxName, contentID, baseImage, commitImage string,
|
||||
) (string, error) {
|
||||
log.Printf(
|
||||
"========>>>> commit devbox, devboxName: %s, contentID: %s, baseImage: %s, commitImage: %s",
|
||||
devboxName,
|
||||
contentID,
|
||||
baseImage,
|
||||
commitImage,
|
||||
)
|
||||
ctx = namespaces.WithNamespace(ctx, DefaultNamespace)
|
||||
containerID, err := c.CreateContainer(ctx, devboxName, contentID, baseImage)
|
||||
if err != nil {
|
||||
return containerID, fmt.Errorf("failed to create container: %w", err)
|
||||
}
|
||||
|
||||
// // mark for gc
|
||||
// defer c.MarkForGC(containerID, commitImage)
|
||||
|
||||
// create commit options
|
||||
global := NewGlobalOptionConfig()
|
||||
opt := types.ContainerCommitOptions{
|
||||
Stdout: io.Discard,
|
||||
GOptions: *global,
|
||||
Pause: PauseContainerDuringCommit,
|
||||
// Remove base image top layer:
|
||||
DevboxOptions: types.DevboxOptions{
|
||||
RemoveBaseImageTopLayer: c.mergeBaseImageTopLayer,
|
||||
},
|
||||
}
|
||||
|
||||
// commit container
|
||||
err = container.Commit(ctx, c.containerdClient, commitImage, containerID, opt)
|
||||
if err != nil {
|
||||
return containerID, fmt.Errorf("failed to commit container: %w", err)
|
||||
}
|
||||
|
||||
return containerID, nil
|
||||
}
|
||||
|
||||
// GetContainerAnnotations get container annotations
|
||||
func (c *CommitterImpl) GetContainerAnnotations(
|
||||
ctx context.Context,
|
||||
containerName string,
|
||||
) (map[string]string, error) {
|
||||
ctx = namespaces.WithNamespace(ctx, DefaultNamespace)
|
||||
container, err := c.containerdClient.LoadContainer(ctx, containerName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load container: %w", err)
|
||||
}
|
||||
|
||||
// get container labels (annotations)
|
||||
labels, err := container.Labels(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get container labels: %w", err)
|
||||
}
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
// Push pushes an image to a remote repository
|
||||
func (c *CommitterImpl) Push(ctx context.Context, imageName string) error {
|
||||
log.Printf("========>>>> push image, imageName: %s", imageName)
|
||||
ctx = namespaces.WithNamespace(ctx, DefaultNamespace)
|
||||
|
||||
// check connection status, if connection is bad, try to reconnect
|
||||
if err := c.CheckConnection(ctx); err != nil {
|
||||
log.Printf("Connection check failed: %v, attempting to reconnect...", err)
|
||||
if reconnectErr := c.Reconnect(ctx); reconnectErr != nil {
|
||||
return fmt.Errorf("failed to reconnect: %w", reconnectErr)
|
||||
}
|
||||
}
|
||||
|
||||
// set resolver
|
||||
resolver, err := GetResolver(ctx, c.registryUsername, c.registryPassword)
|
||||
if err != nil {
|
||||
log.Printf("failed to set resolver, Image: %s, err: %v\n", imageName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
imageRef, err := c.containerdClient.GetImage(ctx, imageName)
|
||||
if err != nil {
|
||||
log.Printf("failed to get image: %s, err: %v\n", imageName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// push image
|
||||
err = c.containerdClient.Push(ctx, imageName, imageRef.Target(),
|
||||
containerd.WithResolver(resolver),
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("failed to push image: %s, err: %v\n", imageName, err)
|
||||
return err
|
||||
}
|
||||
log.Printf("Pushed image success Image: %s\n", imageName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveImage remove image
|
||||
func (c *CommitterImpl) RemoveImages(
|
||||
ctx context.Context,
|
||||
imageNames []string,
|
||||
force, async bool,
|
||||
) error {
|
||||
if len(imageNames) == 0 {
|
||||
return errors.New("[RemoveImages]imageNames is empty")
|
||||
}
|
||||
log.Printf("========>>>> remove image, imageNames: %v", imageNames)
|
||||
ctx = namespaces.WithNamespace(ctx, DefaultNamespace)
|
||||
|
||||
// check connection status, if connection is bad, try to reconnect
|
||||
if err := c.CheckConnection(ctx); err != nil {
|
||||
log.Printf("Connection check failed: %v, attempting to reconnect...", err)
|
||||
if reconnectErr := c.Reconnect(ctx); reconnectErr != nil {
|
||||
return fmt.Errorf("failed to reconnect: %w", reconnectErr)
|
||||
}
|
||||
}
|
||||
|
||||
global := NewGlobalOptionConfig()
|
||||
opt := types.ImageRemoveOptions{
|
||||
Stdout: io.Discard,
|
||||
GOptions: *global,
|
||||
Force: force,
|
||||
Async: async,
|
||||
}
|
||||
return image.Remove(ctx, c.containerdClient, imageNames, opt)
|
||||
}
|
||||
|
||||
// InitializeGC initialize force GC
|
||||
func (c *CommitterImpl) InitializeGC(ctx context.Context) error {
|
||||
gcCtx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
if err := c.forceGC(gcCtx); err != nil {
|
||||
log.Printf("Failed to initialize force GC, err: %v", err)
|
||||
return fmt.Errorf("failed to initialize force GC: %w", err)
|
||||
}
|
||||
log.Println("Force GC initialized successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// forceGC force gc container and image
|
||||
func (c *CommitterImpl) forceGC(ctx context.Context) error {
|
||||
log.Printf("Starting force GC in namespace: %s", DefaultNamespace)
|
||||
ctx = namespaces.WithNamespace(ctx, DefaultNamespace)
|
||||
containers, err := c.containerdClient.Containers(ctx)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get containers, err: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// gc container
|
||||
containerNames := make([]string, 0)
|
||||
for _, container := range containers {
|
||||
containerNames = append(containerNames, container.ID())
|
||||
}
|
||||
if len(containerNames) > 0 {
|
||||
if err := c.RemoveContainers(ctx, containerNames); err != nil {
|
||||
log.Printf("Failed to remove containers, err: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// gc image
|
||||
images, err := c.containerdClient.ListImages(ctx)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get images, err: %v", err)
|
||||
return err
|
||||
}
|
||||
imageNames := make([]string, 0)
|
||||
for _, image := range images {
|
||||
imageNames = append(imageNames, image.Name())
|
||||
}
|
||||
if len(imageNames) > 0 {
|
||||
if err := c.RemoveImages(
|
||||
ctx,
|
||||
imageNames,
|
||||
DefaultRemoveImageForce,
|
||||
DefaultRemoveImageAsync,
|
||||
); err != nil {
|
||||
log.Printf("Failed to remove images, err: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetResolver get resolver
|
||||
func GetResolver(ctx context.Context, username, secret string) (remotes.Resolver, error) {
|
||||
resolverOptions := docker.ResolverOptions{
|
||||
Tracker: docker.NewInMemoryTracker(),
|
||||
}
|
||||
hostOptions := config.HostOptions{}
|
||||
if username == "" && secret == "" {
|
||||
hostOptions.Credentials = nil
|
||||
} else {
|
||||
// TODO: fix this, use flags or configs to set mulit registry credentials
|
||||
hostOptions.Credentials = func(host string) (string, string, error) {
|
||||
return username, secret, nil
|
||||
}
|
||||
}
|
||||
hostOptions.DefaultScheme = "http"
|
||||
hostOptions.DefaultTLS = nil
|
||||
resolverOptions.Hosts = config.ConfigureHosts(ctx, hostOptions)
|
||||
return docker.NewResolver(resolverOptions), nil
|
||||
}
|
||||
|
||||
// convertLabels convert labels to "containerd.io/snapshot/devbox-" format
|
||||
func convertLabels(labels map[string]string) map[string]string {
|
||||
convertedLabels := make(map[string]string)
|
||||
for key, value := range labels {
|
||||
if strings.HasPrefix(key, ContainerLabelPrefix) {
|
||||
// convert "devbox.sealos.io/" to "containerd.io/snapshot/devbox-"
|
||||
newKey := SnapshotLabelPrefix + key[len(ContainerLabelPrefix):]
|
||||
convertedLabels[newKey] = value
|
||||
}
|
||||
}
|
||||
return convertedLabels
|
||||
}
|
||||
|
||||
// convertMapToSlice convert map to slice
|
||||
func convertMapToSlice(labels map[string]string) []string {
|
||||
slice := make([]string, 0, len(labels))
|
||||
for key, value := range labels {
|
||||
slice = append(slice, fmt.Sprintf("%s=%s", key, value))
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
// NewGlobalOptionConfig new global option config
|
||||
func NewGlobalOptionConfig() *types.GlobalCommandOptions {
|
||||
return &types.GlobalCommandOptions{
|
||||
Namespace: DefaultNamespace,
|
||||
Address: DefaultContainerdAddress,
|
||||
DataRoot: DefaultNerdctlDataRoot,
|
||||
Debug: false,
|
||||
DebugFull: false,
|
||||
Snapshotter: DefaultDevboxSnapshotter,
|
||||
CNIPath: ncdefaults.CNIPath(),
|
||||
CNINetConfPath: ncdefaults.CNINetConfPath(),
|
||||
CgroupManager: ncdefaults.CgroupManager(),
|
||||
InsecureRegistry: InsecureRegistry,
|
||||
HostsDir: []string{DefaultNerdctlHostsDir},
|
||||
Experimental: true,
|
||||
HostGatewayIP: ncdefaults.HostGatewayIP(),
|
||||
KubeHideDupe: false,
|
||||
CDISpecDirs: ncdefaults.CDISpecDirs(),
|
||||
UsernsRemap: "",
|
||||
DNS: []string{},
|
||||
DNSOpts: []string{},
|
||||
DNSSearch: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
// CheckConnection check if the connection is still alive
|
||||
func (c *CommitterImpl) CheckConnection(ctx context.Context) error {
|
||||
if c.conn == nil {
|
||||
return errors.New("connection is nil")
|
||||
}
|
||||
|
||||
// check connection state
|
||||
state := c.conn.GetState()
|
||||
if state.String() == "TRANSIENT_FAILURE" || state.String() == "SHUTDOWN" {
|
||||
return fmt.Errorf("connection is in bad state: %s", state.String())
|
||||
}
|
||||
|
||||
// try to ping containerd
|
||||
_, err := c.containerdClient.Version(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to ping containerd: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reconnect attempt to reconnect to containerd
|
||||
func (c *CommitterImpl) Reconnect(ctx context.Context) error {
|
||||
log.Printf("Attempting to reconnect to containerd...")
|
||||
|
||||
// close old connection
|
||||
if c.conn != nil {
|
||||
c.conn.Close()
|
||||
}
|
||||
|
||||
var conn *grpc.ClientConn
|
||||
var err error
|
||||
|
||||
for i := 0; i <= DefaultMaxRetries; i++ {
|
||||
if i > 0 {
|
||||
log.Printf("Retrying connection to containerd (attempt %d/%d)...", i, DefaultMaxRetries)
|
||||
time.Sleep(DefaultRetryDelay)
|
||||
}
|
||||
|
||||
// create gRPC connection
|
||||
conn, err = grpc.NewClient(
|
||||
DefaultContainerdAddress,
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
)
|
||||
if err == nil {
|
||||
log.Printf("Successfully connected to containerd at %s", DefaultContainerdAddress)
|
||||
break
|
||||
}
|
||||
|
||||
log.Printf(
|
||||
"Failed to connect to containerd (attempt %d/%d): %v",
|
||||
i+1,
|
||||
DefaultMaxRetries+1,
|
||||
err,
|
||||
)
|
||||
|
||||
if i == DefaultMaxRetries {
|
||||
return fmt.Errorf(
|
||||
"failed to connect to containerd after %d attempts: %w",
|
||||
DefaultMaxRetries+1,
|
||||
err,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// recreate containerd client
|
||||
containerdClient, err := containerd.NewWithConn(
|
||||
conn,
|
||||
containerd.WithDefaultNamespace(DefaultNamespace),
|
||||
)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return fmt.Errorf("failed to recreate containerd client: %w", err)
|
||||
}
|
||||
|
||||
// update instance
|
||||
c.containerdClient = containerdClient
|
||||
c.conn = conn
|
||||
|
||||
log.Printf("Successfully reconnected to containerd")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close close the connection
|
||||
func (c *CommitterImpl) Close() error {
|
||||
if c.conn != nil {
|
||||
return c.conn.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,824 +0,0 @@
|
||||
package commit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
containerd "github.com/containerd/containerd/v2/client"
|
||||
"github.com/containerd/containerd/v2/core/containers"
|
||||
"github.com/containerd/containerd/v2/pkg/namespaces"
|
||||
"github.com/containerd/errdefs"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
baseImageBusyBox = "docker.io/library/busybox:latest"
|
||||
baseImageNginx = "docker.io/library/nginx:latest"
|
||||
baseImageAlpine = "docker.io/library/alpine:latest"
|
||||
)
|
||||
|
||||
// init Committer
|
||||
func TestNewCommitter(t *testing.T) {
|
||||
committer, err := NewCommitter("", "", "", true)
|
||||
if err != nil {
|
||||
t.Fatalf("NewCommitter failed: %v", err)
|
||||
}
|
||||
assert.NotNil(t, committer)
|
||||
}
|
||||
|
||||
// test commit flow
|
||||
func TestCommitFlow(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// 1. create committer
|
||||
committer, err := NewCommitter("", "", "", true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 2. prepare test data
|
||||
devboxName := fmt.Sprintf("test-devbox-%d", time.Now().Unix())
|
||||
contentID := "test-content-id-123"
|
||||
commitImage := fmt.Sprintf("test-devbox-commit-%d", time.Now().Unix())
|
||||
|
||||
// 3. create container and commit container
|
||||
_, err = committer.Commit(ctx, devboxName, contentID, baseImageBusyBox, commitImage)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// test create container
|
||||
func TestCreateContainer(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
committer, err := NewCommitter("", "", "", true)
|
||||
require.NoError(t, err)
|
||||
|
||||
// create container
|
||||
devboxName := fmt.Sprintf("test-devbox-%d", time.Now().Unix())
|
||||
contentID := fmt.Sprintf("test-content-id-%d", time.Now().Unix())
|
||||
containerID, err := committer.
|
||||
CreateContainer(ctx, devboxName, contentID, baseImageNginx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// verify container labels
|
||||
committerImpl, ok := committer.(*CommitterImpl)
|
||||
if !ok {
|
||||
t.Fatalf("failed to assert committer to CommitterImpl")
|
||||
}
|
||||
var annotations map[string]string
|
||||
if annotations, err = committerImpl.GetContainerAnnotations(ctx, containerID); err != nil {
|
||||
t.Fatalf("GetContainerAnnotations failed: %v", err)
|
||||
}
|
||||
t.Logf("annotations: %+v", annotations)
|
||||
}
|
||||
|
||||
// test delete container
|
||||
func TestDeleteContainer(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
committer, err := NewCommitter("", "", "", true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
committerImpl, ok := committer.(*CommitterImpl)
|
||||
if !ok {
|
||||
t.Fatalf("failed to assert committer to CommitterImpl")
|
||||
}
|
||||
|
||||
// create a container
|
||||
devboxName := fmt.Sprintf("test-devbox-%d", time.Now().Unix())
|
||||
var containerID string
|
||||
if containerID, err = committer.CreateContainer(
|
||||
ctx,
|
||||
devboxName,
|
||||
"test-content-id-789",
|
||||
baseImageAlpine,
|
||||
); err != nil {
|
||||
t.Fatalf("CreateContainer failed: %v", err)
|
||||
}
|
||||
|
||||
// show all containers in current namespace
|
||||
ctx = namespaces.WithNamespace(ctx, DefaultNamespace)
|
||||
var containers []containerd.Container
|
||||
if containers, err = committerImpl.containerdClient.Containers(ctx); err != nil {
|
||||
t.Fatalf("Containers failed: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("=== All Containers in current namespace ===\n")
|
||||
for _, container := range containers {
|
||||
fmt.Printf("Container ID: %s\n", container.ID())
|
||||
}
|
||||
fmt.Printf("=== Total %d containers ===\n", len(containers))
|
||||
|
||||
// delete container
|
||||
if err = committerImpl.DeleteContainer(ctx, containerID); err != nil {
|
||||
t.Fatalf("DeleteContainer failed: %v", err)
|
||||
}
|
||||
|
||||
if containers, err = committerImpl.containerdClient.Containers(ctx); err != nil {
|
||||
t.Fatalf("Containers failed: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("=== All Containers in current namespace ===\n")
|
||||
for _, container := range containers {
|
||||
fmt.Printf("Container ID: %s\n", container.ID())
|
||||
}
|
||||
fmt.Printf("=== Total %d containers ===\n", len(containers))
|
||||
|
||||
// verify container is deleted (try to get labels should return error)
|
||||
if _, err = committerImpl.GetContainerAnnotations(ctx, containerID); err == nil {
|
||||
t.Fatalf("expected error when getting annotations for deleted container")
|
||||
}
|
||||
}
|
||||
|
||||
// test remove container
|
||||
func TestRemoveContainer(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
committer, err := NewCommitter("", "", "", true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
committerImpl, ok := committer.(*CommitterImpl)
|
||||
if !ok {
|
||||
t.Fatalf("failed to assert committer to CommitterImpl")
|
||||
}
|
||||
|
||||
// create a container
|
||||
devboxName := fmt.Sprintf("test-devbox-%d", time.Now().Unix())
|
||||
var containerID string
|
||||
if containerID, err = committer.CreateContainer(
|
||||
ctx,
|
||||
devboxName,
|
||||
"test-content-id-789",
|
||||
baseImageAlpine,
|
||||
); err != nil {
|
||||
t.Fatalf("CreateContainer failed: %v", err)
|
||||
}
|
||||
|
||||
// show all containers in current namespace
|
||||
ctx = namespaces.WithNamespace(ctx, DefaultNamespace)
|
||||
var containers []containerd.Container
|
||||
if containers, err = committerImpl.containerdClient.Containers(ctx); err != nil {
|
||||
t.Fatalf("Containers failed: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("=== All Containers in current namespace ===\n")
|
||||
for _, container := range containers {
|
||||
fmt.Printf("Container ID: %s\n", container.ID())
|
||||
}
|
||||
fmt.Printf("=== Total %d containers ===\n", len(containers))
|
||||
|
||||
// delete container
|
||||
if err = committer.RemoveContainers(ctx, []string{containerID}); err != nil {
|
||||
t.Fatalf("RemoveContainers failed: %v", err)
|
||||
}
|
||||
|
||||
if containers, err = committerImpl.containerdClient.Containers(ctx); err != nil {
|
||||
t.Fatalf("Containers failed: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("=== All Containers in current namespace ===\n")
|
||||
for _, container := range containers {
|
||||
fmt.Printf("Container ID: %s\n", container.ID())
|
||||
}
|
||||
fmt.Printf("=== Total %d containers ===\n", len(containers))
|
||||
|
||||
// verify container is deleted (try to get labels should return error)
|
||||
if _, err = committerImpl.GetContainerAnnotations(ctx, containerID); err == nil {
|
||||
t.Fatalf("expected error when getting annotations for removed container")
|
||||
}
|
||||
}
|
||||
|
||||
// test error cases
|
||||
func TestErrorCases(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
committer, err := NewCommitter("", "", "", true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
committerImpl, ok := committer.(*CommitterImpl)
|
||||
if !ok {
|
||||
t.Fatalf("failed to assert committer to CommitterImpl")
|
||||
}
|
||||
|
||||
// test use not exist image to create container
|
||||
if _, err = committer.CreateContainer(
|
||||
ctx,
|
||||
"test-devbox",
|
||||
"test-content-id",
|
||||
"not-exist-image:latest",
|
||||
); err == nil {
|
||||
t.Fatalf("expected error when creating container with non-exist image")
|
||||
}
|
||||
|
||||
// test use not exist container to delete
|
||||
if err = committerImpl.DeleteContainer(ctx, "not-exist-container"); err == nil {
|
||||
t.Fatalf("expected error when deleting non-exist container")
|
||||
}
|
||||
|
||||
// test get not exist container label
|
||||
if _, err = committerImpl.GetContainerAnnotations(ctx, "not-exist-container"); err == nil {
|
||||
t.Fatalf("expected error when getting annotations of non-exist container")
|
||||
}
|
||||
}
|
||||
|
||||
// test concurrent operations
|
||||
func TestConcurrentOperations(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
committer, err := NewCommitter("", "", "", true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// concurrent to create container
|
||||
containerCount := 3
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(containerCount)
|
||||
var mu sync.Mutex
|
||||
containers := make([]string, containerCount)
|
||||
|
||||
for i := range containerCount {
|
||||
go func(index int) {
|
||||
defer wg.Done()
|
||||
devboxName := fmt.Sprintf("test-devbox-concurrent-%d-%d", time.Now().Unix(), index)
|
||||
var containerID string
|
||||
var err error
|
||||
if containerID, err = committer.CreateContainer(ctx, devboxName,
|
||||
fmt.Sprintf("test-content-id-%d", index),
|
||||
baseImageBusyBox); err != nil {
|
||||
t.Errorf("Failed to create container: %v", err)
|
||||
return
|
||||
}
|
||||
mu.Lock()
|
||||
containers = append(containers, containerID)
|
||||
mu.Unlock()
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// delete containers
|
||||
for _, containerID := range containers {
|
||||
err := committer.RemoveContainers(ctx, []string{containerID})
|
||||
if err != nil {
|
||||
t.Logf("Warning: failed to delete container %s: %v", containerID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// get current containers list
|
||||
committerImpl, ok := committer.(*CommitterImpl)
|
||||
if !ok {
|
||||
t.Fatalf("failed to assert committer to CommitterImpl")
|
||||
}
|
||||
ctx = namespaces.WithNamespace(ctx, DefaultNamespace)
|
||||
var currentContainers []containerd.Container
|
||||
if currentContainers, err = committerImpl.containerdClient.Containers(ctx); err != nil {
|
||||
t.Fatalf("Containers failed: %v", err)
|
||||
}
|
||||
fmt.Printf("=== All Containers in current namespace ===\n")
|
||||
for _, container := range currentContainers {
|
||||
fmt.Printf("Container ID: %s\n", container.ID())
|
||||
}
|
||||
fmt.Printf("=== Total %d containers ===\n", len(currentContainers))
|
||||
}
|
||||
|
||||
// test runtime selection
|
||||
func TestRuntimeSelection(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
committer, err := NewCommitter("", "", "", true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
committerImpl, ok := committer.(*CommitterImpl)
|
||||
if !ok {
|
||||
t.Fatalf("failed to assert committer to CommitterImpl")
|
||||
}
|
||||
|
||||
// create container with specific runtime
|
||||
devboxName := fmt.Sprintf("test-runtime-%d", time.Now().Unix())
|
||||
contentID := "test-runtime-content-id"
|
||||
|
||||
var containerID string
|
||||
if containerID, err = committer.CreateContainer(
|
||||
ctx,
|
||||
devboxName,
|
||||
contentID,
|
||||
baseImageBusyBox,
|
||||
); err != nil {
|
||||
t.Fatalf("CreateContainer failed: %v", err)
|
||||
}
|
||||
assert.NotEmpty(t, containerID)
|
||||
|
||||
// get container info to verify runtime
|
||||
ctx = namespaces.WithNamespace(ctx, DefaultNamespace)
|
||||
var container containerd.Container
|
||||
if container, err = committerImpl.containerdClient.LoadContainer(ctx, containerID); err != nil {
|
||||
t.Fatalf("LoadContainer failed: %v", err)
|
||||
}
|
||||
|
||||
var info containers.Container
|
||||
if info, err = container.Info(ctx); err != nil {
|
||||
t.Fatalf("container.Info failed: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("=== Container Runtime Information ===\n")
|
||||
fmt.Printf("Container ID: %s\n", containerID)
|
||||
fmt.Printf("Runtime Name: %s\n", info.Runtime.Name)
|
||||
fmt.Printf("Runtime Options: %+v\n", info.Runtime.Options)
|
||||
fmt.Printf("Expected Runtime: %s\n", DefaultRuntime)
|
||||
fmt.Printf("Runtime Match: %v\n", info.Runtime.Name == DefaultRuntime)
|
||||
|
||||
// cleanup
|
||||
if err = committerImpl.DeleteContainer(ctx, containerID); err != nil {
|
||||
t.Fatalf("DeleteContainer failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// test connection management
|
||||
func TestConnectionManagement(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
committer, err := NewCommitter("", "", "", true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
committerImpl, ok := committer.(*CommitterImpl)
|
||||
if !ok {
|
||||
t.Fatalf("failed to assert committer to CommitterImpl")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if cerr := committerImpl.Close(); cerr != nil {
|
||||
t.Fatalf("Close failed: %v", cerr)
|
||||
}
|
||||
}()
|
||||
|
||||
// test connection check
|
||||
if err = committerImpl.CheckConnection(ctx); err != nil {
|
||||
t.Fatalf("CheckConnection failed: %v", err)
|
||||
}
|
||||
|
||||
// create container
|
||||
devboxName := fmt.Sprintf("test-devbox-%d", time.Now().Unix())
|
||||
contentID := "903b3c87-1458-4dd8-b0f4-9da7184cf8ca"
|
||||
testImage := "ghcr.io/labring-actions/devbox/go-1.23.0:13aacd8"
|
||||
var containerID string
|
||||
if containerID, err = committer.CreateContainer(
|
||||
ctx,
|
||||
devboxName,
|
||||
contentID,
|
||||
testImage,
|
||||
); err != nil {
|
||||
t.Fatalf("CreateContainer failed: %v", err)
|
||||
}
|
||||
assert.NotEmpty(t, containerID)
|
||||
|
||||
// delete container
|
||||
if err = committer.RemoveContainers(ctx, []string{containerID}); err != nil {
|
||||
t.Fatalf("RemoveContainers failed: %v", err)
|
||||
}
|
||||
|
||||
// test reconnect
|
||||
if err = committerImpl.Reconnect(ctx); err != nil {
|
||||
t.Fatalf("Reconnect failed: %v", err)
|
||||
}
|
||||
|
||||
// create container again
|
||||
if containerID, err = committer.CreateContainer(
|
||||
ctx,
|
||||
devboxName,
|
||||
contentID,
|
||||
testImage,
|
||||
); err != nil {
|
||||
t.Fatalf("CreateContainer failed: %v", err)
|
||||
}
|
||||
assert.NotEmpty(t, containerID)
|
||||
|
||||
// test connection check again
|
||||
if err = committerImpl.CheckConnection(ctx); err != nil {
|
||||
t.Fatalf("CheckConnection failed: %v", err)
|
||||
}
|
||||
|
||||
if err = committer.RemoveContainers(ctx, []string{containerID}); err != nil {
|
||||
t.Fatalf("RemoveContainers failed: %v", err)
|
||||
}
|
||||
|
||||
// test connection check again
|
||||
if err = committerImpl.CheckConnection(ctx); err != nil {
|
||||
t.Fatalf("CheckConnection failed: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Connection management test passed\n")
|
||||
}
|
||||
|
||||
// test push to Docker Hub
|
||||
func TestPushToDockerHub(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// use test registry
|
||||
registryAddr := "docker.io"
|
||||
registryUser := "cunzili"
|
||||
registryPassword := "123456789"
|
||||
|
||||
committer, err := NewCommitter(registryAddr, registryUser, registryPassword, true)
|
||||
if err != nil {
|
||||
t.Errorf("Skip Docker Hub push test: failed to create committer: %v", err)
|
||||
}
|
||||
|
||||
// create a test image name
|
||||
testImageName := fmt.Sprintf("docker.io/cunzili/cunzili:test-%d", time.Now().Unix())
|
||||
|
||||
// create a container and commit it to image
|
||||
devboxName := fmt.Sprintf("test-dockerhub-%d", time.Now().Unix())
|
||||
contentID := fmt.Sprintf("test-dockerhub-content-id-%d", time.Now().Unix())
|
||||
|
||||
containerID, err := committer.Commit(
|
||||
ctx,
|
||||
devboxName,
|
||||
contentID,
|
||||
baseImageBusyBox,
|
||||
testImageName,
|
||||
)
|
||||
if err != nil {
|
||||
t.Errorf("Skip Docker Hub push test: failed to create test image: %v", err)
|
||||
}
|
||||
// containers, err := committer.(*CommitterImpl).containerdClient.Containers(ctx)
|
||||
// assert.NoError(t, err)
|
||||
// fmt.Printf("=== All Containers in current namespace ===\n")
|
||||
// for _, container := range containers {
|
||||
// fmt.Printf("Container ID: %s\n", container.ID())
|
||||
// }
|
||||
// fmt.Printf("=== Total Containers: %d\n", len(containers))
|
||||
|
||||
// push to Docker Hub
|
||||
err = committer.Push(ctx, testImageName)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to push image to Docker Hub: %v", err)
|
||||
} else {
|
||||
fmt.Printf("Successfully pushed image to Docker Hub: %s\n", testImageName)
|
||||
fmt.Printf("You can view the image at: https://hub.docker.com/r/cunzili/cunzili/tags\n")
|
||||
}
|
||||
|
||||
// remove image
|
||||
err = committer.RemoveImages(ctx, []string{testImageName}, false, false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// verify image is deleted
|
||||
committerImpl, ok := committer.(*CommitterImpl)
|
||||
if !ok {
|
||||
t.Fatalf("failed to assert committer to CommitterImpl")
|
||||
}
|
||||
if _, err = committerImpl.containerdClient.GetImage(ctx, testImageName); err == nil {
|
||||
t.Fatalf("expected error when getting deleted image")
|
||||
}
|
||||
fmt.Println("can not find image:", testImageName)
|
||||
|
||||
// remove container
|
||||
if err = committerImpl.RemoveContainers(ctx, []string{containerID}); err != nil {
|
||||
t.Fatalf("RemoveContainers failed: %v", err)
|
||||
}
|
||||
|
||||
// verify container is deleted
|
||||
if _, err = committerImpl.containerdClient.LoadContainer(ctx, containerID); err == nil {
|
||||
t.Fatalf("expected error when loading deleted container")
|
||||
}
|
||||
fmt.Println("can not find container:", containerID)
|
||||
}
|
||||
|
||||
// test push without authentication
|
||||
func TestPushWithoutAuth(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// no authentication
|
||||
registryAddr := "docker.io"
|
||||
registryUser := ""
|
||||
registryPassword := ""
|
||||
|
||||
committer, err := NewCommitter(registryAddr, registryUser, registryPassword, true)
|
||||
if err != nil {
|
||||
t.Skipf("Skip no-auth push test: failed to create committer: %v", err)
|
||||
}
|
||||
|
||||
// use a existing image for test
|
||||
testImageName := "docker.io/cunzili/cunzili:test-no-auth-1754277739"
|
||||
|
||||
// create a container and commit it to image
|
||||
devboxName := fmt.Sprintf("test-no-auth-%d", time.Now().Unix())
|
||||
contentID := "test-no-auth-content-id"
|
||||
|
||||
_, err = committer.Commit(ctx, devboxName, contentID, baseImageBusyBox, testImageName)
|
||||
if err != nil {
|
||||
t.Skipf("Skip no-auth push test: failed to create test image: %v", err)
|
||||
}
|
||||
|
||||
// test push to Docker Hub (no authentication)
|
||||
err = committer.Push(ctx, testImageName)
|
||||
if err != nil {
|
||||
fmt.Printf("Expected error when pushing without auth: %v\n", err)
|
||||
// should fail, because it needs authentication
|
||||
t.Logf("Push failed as expected: %v", err)
|
||||
} else {
|
||||
t.Errorf("Push succeeded unexpectedly without authentication")
|
||||
}
|
||||
}
|
||||
|
||||
// test remove image
|
||||
func TestRemoveImage(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
committer, err := NewCommitter("", "", "", true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
committerImpl, ok := committer.(*CommitterImpl)
|
||||
if !ok {
|
||||
t.Fatalf("failed to assert committer to CommitterImpl")
|
||||
}
|
||||
|
||||
// create a test devbox name and content id
|
||||
devboxName := fmt.Sprintf("test-remove-devbox-%d", time.Now().Unix())
|
||||
contentID := fmt.Sprintf("test-remove-content-id-%d", time.Now().Unix())
|
||||
imageName := fmt.Sprintf("test-remove-image-%d", time.Now().Unix())
|
||||
|
||||
// create a container and commit it to image
|
||||
_, err = committer.Commit(ctx, devboxName, contentID, baseImageBusyBox, imageName)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// // push image
|
||||
// err = committer.Push(ctx, imageName)
|
||||
// assert.NoError(t, err)
|
||||
|
||||
// remove image
|
||||
if err = committer.RemoveImages(ctx, []string{imageName}, false, false); err != nil {
|
||||
t.Fatalf("RemoveImages failed: %v", err)
|
||||
}
|
||||
|
||||
// verify image is deleted
|
||||
if _, err = committerImpl.containerdClient.GetImage(ctx, imageName); err == nil {
|
||||
t.Fatalf("expected error when getting deleted image")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAtomicLabels test containerd's atomic label update
|
||||
func TestAtomicLabels(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ctx = namespaces.WithNamespace(ctx, DefaultNamespace)
|
||||
committer, err := NewCommitter("", "", "", true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 1. create a test container
|
||||
devboxName := fmt.Sprintf("test-atomic-%d", time.Now().Unix())
|
||||
contentID := fmt.Sprintf("test-atomic-content-%d", time.Now().Unix())
|
||||
var containerID string
|
||||
if containerID, err = committer.CreateContainer(
|
||||
ctx,
|
||||
devboxName,
|
||||
contentID,
|
||||
baseImageBusyBox,
|
||||
); err != nil {
|
||||
t.Fatalf("CreateContainer failed: %v", err)
|
||||
}
|
||||
|
||||
// ensure cleanup container after test
|
||||
defer func() {
|
||||
if cleanupErr := committer.RemoveContainers(ctx, []string{containerID}); cleanupErr != nil {
|
||||
fmt.Printf("Failed to cleanup container: %v", cleanupErr)
|
||||
}
|
||||
}()
|
||||
|
||||
// 2. get container object
|
||||
committerImpl, ok := committer.(*CommitterImpl)
|
||||
if !ok {
|
||||
t.Fatalf("failed to assert committer to CommitterImpl")
|
||||
}
|
||||
var container containerd.Container
|
||||
if container, err = committerImpl.containerdClient.LoadContainer(ctx, containerID); err != nil {
|
||||
t.Fatalf("LoadContainer failed: %v", err)
|
||||
}
|
||||
|
||||
// 3. concurrent update label count
|
||||
concurrentUpdates := 10
|
||||
successCount := int32(0)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(concurrentUpdates)
|
||||
|
||||
// 4. concurrent update same label
|
||||
for i := range concurrentUpdates {
|
||||
go func(index int) {
|
||||
defer wg.Done()
|
||||
|
||||
// get current label
|
||||
labels, err := container.Labels(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to get labels: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// copy label map
|
||||
newLabels := make(map[string]string)
|
||||
for k, v := range labels {
|
||||
newLabels[k] = v
|
||||
}
|
||||
|
||||
// add or update test label
|
||||
val1 := fmt.Sprintf("value-%d", index)
|
||||
val2 := time.Now().Format(time.RFC3339Nano)
|
||||
newLabels["test-atomic-label"] = val1
|
||||
newLabels["timestamp"] = val2
|
||||
|
||||
// try to update label
|
||||
_, err = container.SetLabels(ctx, newLabels)
|
||||
if err != nil {
|
||||
if errdefs.IsAlreadyExists(err) {
|
||||
fmt.Printf("Concurrent update detected for index %d\n", index)
|
||||
} else {
|
||||
fmt.Printf("Failed to set labels for index %d: %v\n", index, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// update success count
|
||||
atomic.AddInt32(&successCount, 1)
|
||||
fmt.Printf(
|
||||
"Successfully updated labels for index %d, val1: %s, val2: %s\n",
|
||||
index,
|
||||
val1,
|
||||
val2,
|
||||
)
|
||||
}(i)
|
||||
}
|
||||
|
||||
// wait for all goroutine
|
||||
wg.Wait()
|
||||
|
||||
// verify result
|
||||
finalLabels, err := container.Labels(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
fmt.Printf("Final label value: %s\n", finalLabels["test-atomic-label"])
|
||||
fmt.Printf("Final timestamp: %s\n", finalLabels["timestamp"])
|
||||
fmt.Printf("Successful updates: %d/%d\n", successCount, concurrentUpdates)
|
||||
}
|
||||
|
||||
// test get image
|
||||
func TestGetImage(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
committer, err := NewCommitter("", "", "", true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
committerImpl, ok := committer.(*CommitterImpl)
|
||||
if !ok {
|
||||
t.Fatalf("failed to assert committer to CommitterImpl")
|
||||
}
|
||||
|
||||
ctx = namespaces.WithNamespace(ctx, DefaultNamespace)
|
||||
var images []containerd.Image
|
||||
if images, err = committerImpl.containerdClient.ListImages(ctx); err != nil {
|
||||
t.Fatalf("ListImages failed: %v", err)
|
||||
}
|
||||
for _, image := range images {
|
||||
fmt.Printf("Image ID: %s\n", image.Target().Digest.String())
|
||||
}
|
||||
}
|
||||
|
||||
// TestRemoveImagePerformance test RemoveImage performance
|
||||
func TestRemoveImagePerformance(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
committer, err := NewCommitter("", "", "", true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
committerImpl, ok := committer.(*CommitterImpl)
|
||||
if !ok {
|
||||
t.Fatalf("failed to assert committer to CommitterImpl")
|
||||
}
|
||||
|
||||
ctx = namespaces.WithNamespace(ctx, DefaultNamespace)
|
||||
|
||||
fmt.Printf("\n====================================\n")
|
||||
fmt.Printf("image remove test\n")
|
||||
fmt.Printf("Namespace: %s\n", DefaultNamespace)
|
||||
fmt.Printf("====================================\n\n")
|
||||
|
||||
// list all images
|
||||
var images []containerd.Image
|
||||
if images, err = committerImpl.containerdClient.ListImages(ctx); err != nil {
|
||||
t.Fatalf("ListImages failed: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("current image count: %d\n", len(images))
|
||||
if len(images) > 0 {
|
||||
fmt.Println("\nimage list:")
|
||||
for i, img := range images {
|
||||
fmt.Printf(" [%d] %s\n", i+1, img.Name())
|
||||
size, err := img.Size(ctx)
|
||||
if err == nil {
|
||||
fmt.Printf(" Size: %.2f MB\n", float64(size)/1024/1024)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Println("no image found, test finished")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("\n====================================\n")
|
||||
fmt.Printf("start to remove all images\n")
|
||||
fmt.Printf("====================================\n\n")
|
||||
|
||||
// record each image remove result
|
||||
type RemoveResult struct {
|
||||
ImageName string
|
||||
Size float64
|
||||
Duration time.Duration
|
||||
Success bool
|
||||
Error error
|
||||
}
|
||||
|
||||
results := make([]RemoveResult, 0, len(images))
|
||||
totalStart := time.Now()
|
||||
|
||||
// remove all images
|
||||
for i, image := range images {
|
||||
imageName := image.Name()
|
||||
size, err := image.Size(ctx)
|
||||
sizeMB := float64(size) / 1024 / 1024
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf(
|
||||
"[%d/%d] remove image: %s (size unknown, failed to get size: %v)\n",
|
||||
i+1,
|
||||
len(images),
|
||||
imageName,
|
||||
err,
|
||||
)
|
||||
} else {
|
||||
fmt.Printf("[%d/%d] remove image: %s (%.2f MB)\n", i+1, len(images), imageName, sizeMB)
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
err = committer.RemoveImages(ctx, []string{imageName}, true, false)
|
||||
duration := time.Since(start)
|
||||
|
||||
result := RemoveResult{
|
||||
ImageName: imageName,
|
||||
Size: sizeMB,
|
||||
Duration: duration,
|
||||
Success: err == nil,
|
||||
Error: err,
|
||||
}
|
||||
results = append(results, result)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf(" status: failed - %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" status: success\n")
|
||||
}
|
||||
fmt.Printf(" duration: %v (%.3f seconds)\n\n", duration, duration.Seconds())
|
||||
}
|
||||
|
||||
totalDuration := time.Since(totalStart)
|
||||
|
||||
// output statistics result
|
||||
fmt.Printf("====================================\n")
|
||||
fmt.Printf("remove statistics\n")
|
||||
fmt.Printf("====================================\n\n")
|
||||
|
||||
successCount := 0
|
||||
failCount := 0
|
||||
for _, result := range results {
|
||||
if result.Success {
|
||||
successCount++
|
||||
} else {
|
||||
failCount++
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("total image count: %d\n", len(images))
|
||||
fmt.Printf("success remove: %d\n", successCount)
|
||||
fmt.Printf("failed remove: %d\n", failCount)
|
||||
fmt.Printf("total duration: %v (%.3f seconds)\n\n", totalDuration, totalDuration.Seconds())
|
||||
|
||||
// detailed statistics
|
||||
fmt.Println("each image remove duration:")
|
||||
fmt.Println("-----------------------------------")
|
||||
for i, result := range results {
|
||||
status := "✓"
|
||||
if !result.Success {
|
||||
status = "✗"
|
||||
}
|
||||
fmt.Printf(" [%d] %s %s\n", i+1, status, result.ImageName)
|
||||
fmt.Printf(" Size: %.2f MB\n", result.Size)
|
||||
fmt.Printf(" Time: %v (%.3f seconds)\n", result.Duration, result.Duration.Seconds())
|
||||
if !result.Success {
|
||||
fmt.Printf(" Error: %v\n", result.Error)
|
||||
}
|
||||
}
|
||||
|
||||
// verify remove result
|
||||
fmt.Printf("\n====================================\n")
|
||||
fmt.Printf("verify remove result\n")
|
||||
fmt.Printf("====================================\n")
|
||||
var finalImages []containerd.Image
|
||||
if finalImages, err = committerImpl.containerdClient.ListImages(ctx); err != nil {
|
||||
t.Fatalf("ListImages failed: %v", err)
|
||||
}
|
||||
fmt.Printf("remaining image count after remove: %d\n", len(finalImages))
|
||||
|
||||
if len(finalImages) > 0 {
|
||||
fmt.Println("\nremaining images:")
|
||||
for i, img := range finalImages {
|
||||
fmt.Printf(" [%d] %s\n", i+1, img.Name())
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\ntest finished!\n")
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package commit
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
DefaultNamespace = "k8s.io"
|
||||
DefaultContainerdAddress = "unix:///var/run/containerd/containerd.sock"
|
||||
DefaultRuntime = "io.containerd.runc.v2"
|
||||
DefaultNerdctlDataRoot = "/var/lib/containerd"
|
||||
DefaultNerdctlHostsDir = "/etc/containerd/certs.d"
|
||||
DefaultDevboxSnapshotter = "devbox"
|
||||
DefaultNetworkMode = "none"
|
||||
DefaultRemoveImageAsync = true
|
||||
DefaultRemoveImageForce = false
|
||||
DefaultRemoveContainerForce = false
|
||||
InsecureRegistry = true
|
||||
PauseContainerDuringCommit = false
|
||||
|
||||
AnnotationKeyNamespace = "namespace"
|
||||
AnnotationKeyImageName = "image.name"
|
||||
AnnotationImageFromValue = "true"
|
||||
AnnotationUseLimitValue = "1Gi"
|
||||
DevboxOptionsRemoveBaseImageTopLayer = true
|
||||
|
||||
SnapshotLabelPrefix = "containerd.io/snapshot/devbox-"
|
||||
ContainerLabelPrefix = "devbox.sealos.io/"
|
||||
RemoveContentIDkey = "containerd.io/snapshot/devbox-remove-content-id"
|
||||
|
||||
DefaultMaxRetries = 3
|
||||
DefaultRetryDelay = 5 * time.Second
|
||||
DefaultGcInterval = 20 * time.Minute
|
||||
)
|
||||
@@ -1,910 +0,0 @@
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
devboxv1alpha2 "github.com/labring/sealos/controllers/devbox/api/v1alpha2"
|
||||
"github.com/labring/sealos/controllers/devbox/internal/controller/utils/events"
|
||||
"github.com/labring/sealos/controllers/devbox/internal/controller/utils/matcher"
|
||||
"github.com/labring/sealos/controllers/devbox/internal/controller/utils/resource"
|
||||
"github.com/labring/sealos/controllers/devbox/internal/controller/utils/rwords"
|
||||
"github.com/labring/sealos/controllers/devbox/internal/stat"
|
||||
"github.com/labring/sealos/controllers/devbox/label"
|
||||
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"
|
||||
"k8s.io/apimachinery/pkg/util/rand"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/util/retry"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
)
|
||||
|
||||
// DevboxReconciler reconciles a Devbox object
|
||||
type DevboxReconciler struct {
|
||||
CommitImageRegistry string
|
||||
DevboxNodeLabel string
|
||||
NodeName string
|
||||
|
||||
RequestRate resource.RequestRate
|
||||
EphemeralStorage resource.EphemeralStorage
|
||||
|
||||
PodMatchers []matcher.PodMatcher
|
||||
|
||||
DebugMode bool
|
||||
MergeBaseImageTopLayer bool
|
||||
EnableBlockIOResource bool
|
||||
StartupConfigMapName string
|
||||
StartupConfigMapNamespace string
|
||||
|
||||
client.Client
|
||||
Scheme *runtime.Scheme
|
||||
Recorder record.EventRecorder
|
||||
StateChangeRecorder record.EventRecorder
|
||||
|
||||
RestartPredicateDuration time.Duration
|
||||
AcceptanceThreshold int
|
||||
stat.NodeStatsProvider
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=devbox.sealos.io,resources=devboxes,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=devbox.sealos.io,resources=devboxes/status,verbs=get;update;patch
|
||||
// +kubebuilder:rbac:groups=devbox.sealos.io,resources=devboxes/finalizers,verbs=update
|
||||
// +kubebuilder:rbac:groups=devbox.sealos.io,resources=runtimes,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=devbox.sealos.io,resources=runtimeclasses,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups="",resources=pods,verbs=*
|
||||
// +kubebuilder:rbac:groups="",resources=pods/status,verbs=get;update;patch
|
||||
// +kubebuilder:rbac:groups="",resources=nodes,verbs=get;list;watch
|
||||
// +kubebuilder:rbac:groups="",resources=nodes/status,verbs=get
|
||||
// +kubebuilder:rbac:groups="",resources=services,verbs=*
|
||||
// +kubebuilder:rbac:groups="",resources=secrets,verbs=*
|
||||
// +kubebuilder:rbac:groups="",resources=configmaps,verbs=*
|
||||
// +kubebuilder:rbac:groups="",resources=events,verbs=*
|
||||
|
||||
func (r *DevboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
logger := log.FromContext(ctx).WithValues("devbox", req.NamespacedName)
|
||||
|
||||
// 1) Fetch the object. If it's gone, nothing to do.
|
||||
devbox := &devboxv1alpha2.Devbox{}
|
||||
if err := r.Get(ctx, req.NamespacedName, devbox); err != nil {
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
|
||||
recLabels := label.RecommendedLabels(&label.Recommended{
|
||||
Name: devbox.Name,
|
||||
ManagedBy: label.DefaultManagedBy,
|
||||
PartOf: devboxv1alpha2.LabelDevBoxPartOf,
|
||||
})
|
||||
|
||||
logger.Info("start reconciling devbox")
|
||||
if r.StartupConfigMapName != "" {
|
||||
logger.Info(
|
||||
"startup config map set",
|
||||
"startupConfigMapName", r.StartupConfigMapName,
|
||||
"startupConfigMapNamespace", r.StartupConfigMapNamespace,
|
||||
)
|
||||
}
|
||||
|
||||
// 2) Deletion flow: make best-effort to delete sub-resources, then remove finalizer.
|
||||
if !devbox.DeletionTimestamp.IsZero() {
|
||||
return r.reconcileDevboxDeletion(ctx, devbox, recLabels)
|
||||
}
|
||||
|
||||
// 3) Ensure finalizer exists (idempotent).
|
||||
if err := r.ensureDevboxFinalizer(ctx, req.NamespacedName); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// 4) Per-node controller ownership: if another node already owns this devbox, we should not reconcile it.
|
||||
if devbox.Status.Node != "" && devbox.Status.Node != r.NodeName {
|
||||
logger.Info(
|
||||
"devbox already scheduled to another node, skip reconcile",
|
||||
"node",
|
||||
devbox.Status.Node,
|
||||
)
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// 5) Initialize status (idempotent). If we updated status, requeue to continue with the persisted status.
|
||||
updated, err := r.initDevboxStatus(ctx, devbox)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
if updated {
|
||||
return ctrl.Result{Requeue: true}, nil
|
||||
}
|
||||
|
||||
// 6) Validate required status fields for the rest of the flow.
|
||||
commitRecord, requeue, err := r.getCurrentCommitRecord(devbox)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
if requeue {
|
||||
logger.Info("commit record is not found, requeue to wait for commit record to be created")
|
||||
return ctrl.Result{Requeue: true}, nil
|
||||
}
|
||||
|
||||
// 7) Scheduling/claiming ownership (only when running).
|
||||
if devbox.Spec.State == devboxv1alpha2.DevboxStateRunning {
|
||||
res, err := r.ensureDevboxScheduledToThisNodeIfPossible(
|
||||
ctx,
|
||||
req.NamespacedName,
|
||||
devbox,
|
||||
commitRecord,
|
||||
)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
if res.Requeue || res.RequeueAfter > 0 {
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 8) Reconcile desired resources (pods/services/secrets/etc).
|
||||
if err := r.runSyncPipeline(ctx, devbox, recLabels); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// 9) State transition observability (emit event once per generation).
|
||||
if err := r.maybeEmitStateChangeEvent(ctx, devbox); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// 10) Keep conditions/ObservedGeneration in sync.
|
||||
if err := r.syncDevboxConditions(ctx, devbox); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
logger.Info("devbox reconcile success")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
func (r *DevboxReconciler) initDevboxStatus(
|
||||
ctx context.Context,
|
||||
devbox *devboxv1alpha2.Devbox,
|
||||
) (updated bool, err error) {
|
||||
// only fill missing fields; avoid overriding existing status
|
||||
changed := false
|
||||
|
||||
// init devbox status network type
|
||||
if devbox.Status.Network.Type == "" {
|
||||
devbox.Status.Network.Type = devbox.Spec.NetworkSpec.Type
|
||||
changed = true
|
||||
}
|
||||
|
||||
// init devbox status content id
|
||||
if devbox.Status.ContentID == "" {
|
||||
devbox.Status.ContentID = uuid.New().String()
|
||||
changed = true
|
||||
}
|
||||
currentContentID := devbox.Status.ContentID
|
||||
|
||||
// init devbox status commit record map
|
||||
if devbox.Status.CommitRecords == nil {
|
||||
devbox.Status.CommitRecords = make(map[string]*devboxv1alpha2.CommitRecord)
|
||||
changed = true
|
||||
}
|
||||
|
||||
// init devbox status commit record for current content id
|
||||
if devbox.Status.CommitRecords[currentContentID] == nil {
|
||||
devbox.Status.CommitRecords[currentContentID] = &devboxv1alpha2.CommitRecord{
|
||||
Node: "",
|
||||
BaseImage: devbox.Spec.Image,
|
||||
CommitImage: r.generateImageName(devbox),
|
||||
CommitStatus: devboxv1alpha2.CommitStatusPending,
|
||||
GenerateTime: metav1.Now(),
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
|
||||
// init devbox status state
|
||||
if devbox.Status.State == "" {
|
||||
devbox.Status.State = devbox.Spec.State
|
||||
changed = true
|
||||
}
|
||||
|
||||
// init devbox status network unique id
|
||||
if devbox.Status.Network.UniqueID == "" {
|
||||
devbox.Status.Network.UniqueID = rwords.GenerateRandomWords()
|
||||
changed = true
|
||||
}
|
||||
|
||||
// update devbox status, and do not return error to avoid infinite loop because multiple controller will reconcile this devbox
|
||||
if changed {
|
||||
if err := r.Status().Update(ctx, devbox); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// reconcileDevboxDeletion deletes owned resources then removes the devbox finalizer.
|
||||
func (r *DevboxReconciler) reconcileDevboxDeletion(
|
||||
ctx context.Context,
|
||||
devbox *devboxv1alpha2.Devbox,
|
||||
recLabels map[string]string,
|
||||
) (ctrl.Result, error) {
|
||||
logger := log.FromContext(ctx).WithValues("devbox", client.ObjectKeyFromObject(devbox))
|
||||
|
||||
logger.Info("devbox deleted, remove all resources")
|
||||
if err := r.handleSubResourceDelete(ctx, devbox, recLabels); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// delete storage:
|
||||
if err := r.handleStorageDelete(ctx, devbox); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
logger.Info("devbox deleted, remove finalizer")
|
||||
if controllerutil.RemoveFinalizer(devbox, devboxv1alpha2.FinalizerName) {
|
||||
if err := r.Update(ctx, devbox); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// ensureDevboxFinalizer ensures the devbox has the controller finalizer (idempotent).
|
||||
func (r *DevboxReconciler) ensureDevboxFinalizer(ctx context.Context, key client.ObjectKey) error {
|
||||
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
latest := &devboxv1alpha2.Devbox{}
|
||||
if err := r.Get(ctx, key, latest); err != nil {
|
||||
return client.IgnoreNotFound(err)
|
||||
}
|
||||
if !controllerutil.ContainsFinalizer(latest, devboxv1alpha2.FinalizerName) {
|
||||
controllerutil.AddFinalizer(latest, devboxv1alpha2.FinalizerName)
|
||||
return r.Update(ctx, latest)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// getCurrentCommitRecord returns the current commit record.
|
||||
// If it is missing, caller should requeue (not an error).
|
||||
func (r *DevboxReconciler) getCurrentCommitRecord(
|
||||
devbox *devboxv1alpha2.Devbox,
|
||||
) (record *devboxv1alpha2.CommitRecord, requeue bool, err error) {
|
||||
if devbox.Status.ContentID == "" {
|
||||
return nil, true, nil
|
||||
}
|
||||
if devbox.Status.CommitRecords == nil {
|
||||
return nil, true, nil
|
||||
}
|
||||
rec := devbox.Status.CommitRecords[devbox.Status.ContentID]
|
||||
if rec == nil {
|
||||
return nil, true, nil
|
||||
}
|
||||
return rec, false, nil
|
||||
}
|
||||
|
||||
// ensureDevboxScheduledToThisNodeIfPossible tries to claim ownership for a running devbox.
|
||||
// It returns a ctrl.Result to requeue/slow-requeue when needed.
|
||||
func (r *DevboxReconciler) ensureDevboxScheduledToThisNodeIfPossible(
|
||||
ctx context.Context,
|
||||
key client.ObjectKey,
|
||||
devbox *devboxv1alpha2.Devbox,
|
||||
commitRecord *devboxv1alpha2.CommitRecord,
|
||||
) (ctrl.Result, error) {
|
||||
logger := log.FromContext(ctx).WithValues("devbox", key)
|
||||
|
||||
// If the current content is owned by another node, skip.
|
||||
if commitRecord.Node != "" && commitRecord.Node != r.NodeName {
|
||||
logger.Info("devbox already scheduled to node", "node", commitRecord.Node)
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// Already ours: continue.
|
||||
if commitRecord.Node == r.NodeName {
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// Try to claim ownership when unscheduled.
|
||||
score := r.getAcceptanceScore(ctx, devbox)
|
||||
if score < r.AcceptanceThreshold {
|
||||
logger.Info("devbox not scheduled to node, try scheduling to us later",
|
||||
"nodeName", r.NodeName,
|
||||
"score", score,
|
||||
"acceptanceThreshold", r.AcceptanceThreshold)
|
||||
return ctrl.Result{RequeueAfter: 5 * time.Second}, nil
|
||||
}
|
||||
|
||||
logger.Info("devbox not scheduled to node, try scheduling to us now",
|
||||
"nodeName", r.NodeName,
|
||||
"score", score,
|
||||
"acceptanceThreshold", r.AcceptanceThreshold)
|
||||
|
||||
claimedByUs := false
|
||||
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
latest := &devboxv1alpha2.Devbox{}
|
||||
if err := r.Get(ctx, key, latest); err != nil {
|
||||
return err
|
||||
}
|
||||
if latest.Status.CommitRecords == nil ||
|
||||
latest.Status.CommitRecords[latest.Status.ContentID] == nil {
|
||||
return fmt.Errorf("commit record missing for contentID %s", latest.Status.ContentID)
|
||||
}
|
||||
|
||||
latestRecord := latest.Status.CommitRecords[latest.Status.ContentID]
|
||||
// Someone else (or us) already claimed it.
|
||||
if latestRecord.Node != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
latestRecord.Node = r.NodeName
|
||||
latest.Status.Node = r.NodeName
|
||||
if err := r.Status().Update(ctx, latest); err != nil {
|
||||
return err
|
||||
}
|
||||
claimedByUs = true
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// If we claimed it, requeue to continue with persisted status and emit event once.
|
||||
if claimedByUs {
|
||||
r.Recorder.Eventf(
|
||||
devbox,
|
||||
corev1.EventTypeNormal,
|
||||
"Devbox scheduled to node",
|
||||
"Devbox scheduled to node",
|
||||
)
|
||||
return ctrl.Result{Requeue: true}, nil
|
||||
}
|
||||
|
||||
// Someone else claimed it; stop reconciling on this node.
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// maybeEmitStateChangeEvent records state change event once per generation when this node is allowed to sync.
|
||||
func (r *DevboxReconciler) maybeEmitStateChangeEvent(
|
||||
ctx context.Context,
|
||||
devbox *devboxv1alpha2.Devbox,
|
||||
) error {
|
||||
logger := log.FromContext(ctx).WithValues("devbox", client.ObjectKeyFromObject(devbox))
|
||||
|
||||
if devbox.Status.CommitRecords == nil ||
|
||||
devbox.Status.CommitRecords[devbox.Status.ContentID] == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Only the node that owns the current content should sync state.
|
||||
// Exception: for stop/shutdown transitions, allow syncing when not yet scheduled.
|
||||
contentID := devbox.Status.ContentID
|
||||
currentRecord := devbox.Status.CommitRecords[contentID]
|
||||
ownedByThisNode := currentRecord.Node == r.NodeName
|
||||
stopOrShutdown := devbox.Spec.State == devboxv1alpha2.DevboxStateStopped ||
|
||||
devbox.Spec.State == devboxv1alpha2.DevboxStateShutdown
|
||||
unscheduled := currentRecord.Node == ""
|
||||
allowedToSyncState := ownedByThisNode || (stopOrShutdown && unscheduled)
|
||||
needsStateTransition := devbox.Spec.State != devbox.Status.State
|
||||
|
||||
if !allowedToSyncState || !needsStateTransition {
|
||||
return nil
|
||||
}
|
||||
|
||||
shouldEmit, err := r.markStateTransitionPendingAndReturnShouldEmit(ctx, devbox)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !shouldEmit {
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.Info(
|
||||
"recording state change event for devbox",
|
||||
"devbox",
|
||||
devbox.Name,
|
||||
"from",
|
||||
devbox.Status.State,
|
||||
"to",
|
||||
devbox.Spec.State,
|
||||
)
|
||||
r.StateChangeRecorder.Eventf(
|
||||
devbox,
|
||||
corev1.EventTypeNormal,
|
||||
events.ReasonDevboxStateChanged,
|
||||
"Devbox state changed from %s to %s",
|
||||
devbox.Status.State,
|
||||
devbox.Spec.State,
|
||||
)
|
||||
r.Recorder.Eventf(
|
||||
devbox,
|
||||
corev1.EventTypeNormal,
|
||||
events.ReasonDevboxStateChanged,
|
||||
"Devbox state changed from %s to %s",
|
||||
devbox.Status.State,
|
||||
devbox.Spec.State,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *DevboxReconciler) markStateTransitionPendingAndReturnShouldEmit(
|
||||
ctx context.Context,
|
||||
devbox *devboxv1alpha2.Devbox,
|
||||
) (bool, error) {
|
||||
var shouldEmit bool
|
||||
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
latest := &devboxv1alpha2.Devbox{}
|
||||
if err := r.Get(ctx, client.ObjectKeyFromObject(devbox), latest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the state is already synced, there's nothing to emit.
|
||||
if latest.Spec.State == latest.Status.State {
|
||||
shouldEmit = false
|
||||
return nil
|
||||
}
|
||||
|
||||
latest.SetCondition(metav1.Condition{
|
||||
Type: devboxv1alpha2.DevboxConditionStateTransitionPending,
|
||||
Status: metav1.ConditionTrue,
|
||||
ObservedGeneration: latest.Generation,
|
||||
Reason: devboxv1alpha2.DevboxReasonSpecStateChanged,
|
||||
Message: "spec.state differs from status.state; state transition pending",
|
||||
LastTransitionTime: metav1.Now(),
|
||||
})
|
||||
shouldEmit = true
|
||||
return r.Status().Update(ctx, latest)
|
||||
})
|
||||
return shouldEmit, err
|
||||
}
|
||||
|
||||
func (r *DevboxReconciler) syncDevboxConditions(
|
||||
ctx context.Context,
|
||||
devbox *devboxv1alpha2.Devbox,
|
||||
) error {
|
||||
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
latest := &devboxv1alpha2.Devbox{}
|
||||
if err := r.Get(ctx, client.ObjectKeyFromObject(devbox), latest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Only advance ObservedGeneration when the state transition is fully synced.
|
||||
// (Other spec changes are currently reconciled in the same flow; using state
|
||||
// as the hard gate avoids reporting a generation as "observed" while a
|
||||
// transition is still pending.)
|
||||
if latest.Spec.State == latest.Status.State {
|
||||
latest.Status.ObservedGeneration = latest.Generation
|
||||
}
|
||||
|
||||
// State transition pending condition
|
||||
if latest.Spec.State != latest.Status.State {
|
||||
latest.SetCondition(metav1.Condition{
|
||||
Type: devboxv1alpha2.DevboxConditionStateTransitionPending,
|
||||
Status: metav1.ConditionTrue,
|
||||
ObservedGeneration: latest.Generation,
|
||||
Reason: devboxv1alpha2.DevboxReasonSpecStateChanged,
|
||||
Message: "spec.state differs from status.state; state transition pending",
|
||||
LastTransitionTime: metav1.Now(),
|
||||
})
|
||||
} else {
|
||||
latest.SetCondition(metav1.Condition{
|
||||
Type: devboxv1alpha2.DevboxConditionStateTransitionPending,
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: latest.Generation,
|
||||
Reason: devboxv1alpha2.DevboxReasonStateTransitionSynced,
|
||||
Message: "spec.state matches status.state",
|
||||
LastTransitionTime: metav1.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
// Commit in progress condition (authoritative record is the current ContentID).
|
||||
// Guard against missing commit record to avoid panics.
|
||||
var committing bool
|
||||
if latest.Status.CommitRecords != nil && latest.Status.ContentID != "" {
|
||||
if rec := latest.Status.CommitRecords[latest.Status.ContentID]; rec != nil {
|
||||
committing = rec.CommitStatus == devboxv1alpha2.CommitStatusCommitting
|
||||
}
|
||||
}
|
||||
if committing {
|
||||
latest.SetCondition(metav1.Condition{
|
||||
Type: devboxv1alpha2.DevboxConditionCommitInProgress,
|
||||
Status: metav1.ConditionTrue,
|
||||
ObservedGeneration: latest.Generation,
|
||||
Reason: devboxv1alpha2.DevboxReasonCommitStarted,
|
||||
Message: "commit workflow in progress",
|
||||
LastTransitionTime: metav1.Now(),
|
||||
})
|
||||
} else {
|
||||
latest.SetCondition(metav1.Condition{
|
||||
Type: devboxv1alpha2.DevboxConditionCommitInProgress,
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: latest.Generation,
|
||||
Reason: devboxv1alpha2.DevboxReasonCommitNotInProgress,
|
||||
Message: "no commit workflow in progress",
|
||||
LastTransitionTime: metav1.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
return r.Status().Update(ctx, latest)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *DevboxReconciler) handleStorageDelete(
|
||||
ctx context.Context,
|
||||
devbox *devboxv1alpha2.Devbox,
|
||||
) error {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
// Early return if storage is already cleaned up
|
||||
if r.isStorageAlreadyCleanedUp(devbox) {
|
||||
logger.Info("devbox storage already cleaned up, skipping cleanup",
|
||||
"devbox", devbox.Name,
|
||||
"state", devbox.Status.State)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate and get commit record
|
||||
commitRecord, err := r.validateAndGetCommitRecord(devbox)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to validate commit record", "devbox", devbox.Name)
|
||||
return err
|
||||
}
|
||||
// Check if this node should handle the cleanup
|
||||
if !r.shouldHandleStorageCleanup(commitRecord) {
|
||||
logger.Info("skipping storage cleanup - not responsible node",
|
||||
"devbox", devbox.Name,
|
||||
"commitRecordNode", commitRecord.Node,
|
||||
"currentNode", r.NodeName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Request storage cleanup
|
||||
return r.requestStorageCleanup(ctx, devbox, commitRecord)
|
||||
}
|
||||
|
||||
// isStorageAlreadyCleanedUp checks if storage cleanup is already done
|
||||
// shutdown or stopped devbox is already cleaned up
|
||||
func (r *DevboxReconciler) isStorageAlreadyCleanedUp(devbox *devboxv1alpha2.Devbox) bool {
|
||||
return devbox.Status.State == devboxv1alpha2.DevboxStateShutdown ||
|
||||
devbox.Status.State == devboxv1alpha2.DevboxStateStopped
|
||||
}
|
||||
|
||||
// validateAndGetCommitRecord validates devbox status and returns the current commit record
|
||||
func (r *DevboxReconciler) validateAndGetCommitRecord(
|
||||
devbox *devboxv1alpha2.Devbox,
|
||||
) (*devboxv1alpha2.CommitRecord, error) {
|
||||
contentID := devbox.Status.ContentID
|
||||
if contentID == "" {
|
||||
return nil, fmt.Errorf("contentID is empty for devbox %s", devbox.Name)
|
||||
}
|
||||
|
||||
if devbox.Status.CommitRecords == nil {
|
||||
return nil, fmt.Errorf("commit records is nil for devbox %s", devbox.Name)
|
||||
}
|
||||
|
||||
commitRecord, exists := devbox.Status.CommitRecords[contentID]
|
||||
if !exists || commitRecord == nil {
|
||||
return nil, fmt.Errorf(
|
||||
"commit record not found for contentID %s in devbox %s",
|
||||
contentID,
|
||||
devbox.Name,
|
||||
)
|
||||
}
|
||||
|
||||
if commitRecord.BaseImage == "" {
|
||||
return nil, fmt.Errorf("baseImage is empty in commit record for devbox %s", devbox.Name)
|
||||
}
|
||||
|
||||
return commitRecord, nil
|
||||
}
|
||||
|
||||
// shouldHandleStorageCleanup determines if the current node should handle storage cleanup
|
||||
func (r *DevboxReconciler) shouldHandleStorageCleanup(
|
||||
commitRecord *devboxv1alpha2.CommitRecord,
|
||||
) bool {
|
||||
return commitRecord.Node == r.NodeName
|
||||
}
|
||||
|
||||
// requestStorageCleanup sends a storage cleanup request via event recorder
|
||||
func (r *DevboxReconciler) requestStorageCleanup(
|
||||
ctx context.Context,
|
||||
devbox *devboxv1alpha2.Devbox,
|
||||
commitRecord *devboxv1alpha2.CommitRecord,
|
||||
) error {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
logger.Info("requesting devbox storage cleanup",
|
||||
"devbox", devbox.Name,
|
||||
"contentID", devbox.Status.ContentID,
|
||||
"baseImage", commitRecord.BaseImage)
|
||||
|
||||
r.StateChangeRecorder.AnnotatedEventf(
|
||||
devbox,
|
||||
events.BuildStorageCleanupAnnotations(
|
||||
devbox.Name,
|
||||
devbox.Status.ContentID,
|
||||
commitRecord.BaseImage,
|
||||
),
|
||||
corev1.EventTypeNormal,
|
||||
events.ReasonStorageCleanupRequested,
|
||||
"devbox storage cleanup requested",
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *DevboxReconciler) generateImageName(devbox *devboxv1alpha2.Devbox) string {
|
||||
now := time.Now()
|
||||
return fmt.Sprintf(
|
||||
"%s/%s/%s:%s-%s",
|
||||
r.CommitImageRegistry,
|
||||
devbox.Namespace,
|
||||
devbox.Name,
|
||||
rand.String(5),
|
||||
now.Format("2006-01-02-150405"),
|
||||
)
|
||||
}
|
||||
|
||||
func (r *DevboxReconciler) handleSubResourceDelete(
|
||||
ctx context.Context,
|
||||
devbox *devboxv1alpha2.Devbox,
|
||||
recLabels map[string]string,
|
||||
) error {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
// Delete Pod
|
||||
podList := &corev1.PodList{}
|
||||
if err := r.List(
|
||||
ctx,
|
||||
podList,
|
||||
client.InNamespace(devbox.Namespace),
|
||||
client.MatchingLabels(recLabels),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range podList.Items {
|
||||
pod := &podList.Items[i]
|
||||
originalPodUID := pod.UID
|
||||
|
||||
// Remove finalizer with retry and UID check
|
||||
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
latestPod := &corev1.Pod{}
|
||||
if err := r.Get(ctx, client.ObjectKeyFromObject(pod), latestPod); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
// Pod already deleted
|
||||
logger.Info("pod already deleted, skip finalizer removal", "pod", pod.Name)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if UID matches
|
||||
if latestPod.UID != originalPodUID {
|
||||
logger.Info("pod UID changed, skip finalizer removal",
|
||||
"pod", pod.Name,
|
||||
"originalUID", originalPodUID,
|
||||
"currentUID", latestPod.UID)
|
||||
return nil
|
||||
}
|
||||
|
||||
if controllerutil.RemoveFinalizer(latestPod, devboxv1alpha2.FinalizerName) {
|
||||
return r.Update(ctx, latestPod)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to remove finalizer from pod", "pod", pod.Name)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := r.deleteResourcesByLabels(
|
||||
ctx,
|
||||
&corev1.Pod{},
|
||||
devbox.Namespace,
|
||||
recLabels,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
// Delete Service
|
||||
if err := r.deleteResourcesByLabels(
|
||||
ctx,
|
||||
&corev1.Service{},
|
||||
devbox.Namespace,
|
||||
recLabels,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
// Delete Configmap
|
||||
if err := r.deleteResourcesByLabels(
|
||||
ctx,
|
||||
&corev1.ConfigMap{},
|
||||
devbox.Namespace,
|
||||
recLabels,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
// Delete Secret
|
||||
return r.deleteResourcesByLabels(ctx, &corev1.Secret{}, devbox.Namespace, recLabels)
|
||||
}
|
||||
|
||||
func (r *DevboxReconciler) deleteResourcesByLabels(
|
||||
ctx context.Context,
|
||||
obj client.Object,
|
||||
namespace string,
|
||||
labels map[string]string,
|
||||
) error {
|
||||
err := r.DeleteAllOf(ctx, obj,
|
||||
client.InNamespace(namespace),
|
||||
client.MatchingLabels(labels),
|
||||
)
|
||||
return client.IgnoreNotFound(err)
|
||||
}
|
||||
|
||||
func (r *DevboxReconciler) setConditionWithRetry(
|
||||
ctx context.Context,
|
||||
devbox *devboxv1alpha2.Devbox,
|
||||
cond metav1.Condition,
|
||||
) error {
|
||||
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
latest := &devboxv1alpha2.Devbox{}
|
||||
if err := r.Get(ctx, client.ObjectKeyFromObject(devbox), latest); err != nil {
|
||||
return err
|
||||
}
|
||||
cond.ObservedGeneration = latest.Generation
|
||||
cond.LastTransitionTime = metav1.Now()
|
||||
latest.SetCondition(cond)
|
||||
return r.Status().Update(ctx, latest)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *DevboxReconciler) setSyncCondition(
|
||||
ctx context.Context,
|
||||
devbox *devboxv1alpha2.Devbox,
|
||||
conditionType string,
|
||||
ok bool,
|
||||
message string,
|
||||
) {
|
||||
logger := log.FromContext(ctx)
|
||||
status := metav1.ConditionFalse
|
||||
reason := devboxv1alpha2.DevboxReasonSyncFailed
|
||||
if ok {
|
||||
status = metav1.ConditionTrue
|
||||
reason = devboxv1alpha2.DevboxReasonSyncSucceeded
|
||||
}
|
||||
if err := r.setConditionWithRetry(ctx, devbox, metav1.Condition{
|
||||
Type: conditionType,
|
||||
Status: status,
|
||||
Reason: reason,
|
||||
Message: message,
|
||||
}); err != nil {
|
||||
logger.Info(
|
||||
"failed to update condition (best-effort)",
|
||||
"conditionType",
|
||||
conditionType,
|
||||
"error",
|
||||
err,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ContentIDChangedPredicate triggers reconcile when devbox status.contentID changes
|
||||
type ContentIDChangedPredicate struct {
|
||||
predicate.Funcs
|
||||
}
|
||||
|
||||
func (p ContentIDChangedPredicate) Update(e event.UpdateEvent) bool {
|
||||
if e.ObjectOld == nil || e.ObjectNew == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
oldDevbox, oldOk := e.ObjectOld.(*devboxv1alpha2.Devbox)
|
||||
newDevbox, newOk := e.ObjectNew.(*devboxv1alpha2.Devbox)
|
||||
if oldOk && newOk {
|
||||
return oldDevbox.Status.ContentID != newDevbox.Status.ContentID
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// LastContainerStatusChangedPredicate triggers reconcile when devbox status.lastContainerStatus changes
|
||||
type LastContainerStatusChangedPredicate struct {
|
||||
predicate.Funcs
|
||||
}
|
||||
|
||||
func (p LastContainerStatusChangedPredicate) Update(e event.UpdateEvent) bool {
|
||||
if e.ObjectOld == nil || e.ObjectNew == nil {
|
||||
return false
|
||||
}
|
||||
oldDevbox, oldOk := e.ObjectOld.(*devboxv1alpha2.Devbox)
|
||||
newDevbox, newOk := e.ObjectNew.(*devboxv1alpha2.Devbox)
|
||||
if oldOk && newOk {
|
||||
return oldDevbox.Status.LastContainerStatus.ContainerID != newDevbox.Status.LastContainerStatus.ContainerID
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NetworkTypeChangedPredicate triggers reconcile when devbox status.network.type changes
|
||||
type NetworkTypeChangedPredicate struct {
|
||||
predicate.Funcs
|
||||
}
|
||||
|
||||
func (p NetworkTypeChangedPredicate) Update(e event.UpdateEvent) bool {
|
||||
if e.ObjectOld == nil || e.ObjectNew == nil {
|
||||
return false
|
||||
}
|
||||
oldDevbox, oldOk := e.ObjectOld.(*devboxv1alpha2.Devbox)
|
||||
newDevbox, newOk := e.ObjectNew.(*devboxv1alpha2.Devbox)
|
||||
if oldOk && newOk {
|
||||
return oldDevbox.Status.Network.Type != newDevbox.Status.Network.Type
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// PhaseChangedPredicate triggers reconcile when devbox status.phase changes or status.phase is `Error`
|
||||
type PhaseChangedPredicate struct {
|
||||
predicate.Funcs
|
||||
}
|
||||
|
||||
func (p PhaseChangedPredicate) Update(e event.UpdateEvent) bool {
|
||||
if e.ObjectOld == nil || e.ObjectNew == nil {
|
||||
return false
|
||||
}
|
||||
oldDevbox, oldOk := e.ObjectOld.(*devboxv1alpha2.Devbox)
|
||||
newDevbox, newOk := e.ObjectNew.(*devboxv1alpha2.Devbox)
|
||||
if oldOk && newOk {
|
||||
return oldDevbox.Status.Phase != newDevbox.Status.Phase ||
|
||||
newDevbox.Status.Phase == devboxv1alpha2.DevboxPhaseError
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetupWithManager sets up the controller with the Manager.
|
||||
func (r *DevboxReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
if err := mgr.GetFieldIndexer().
|
||||
IndexField(context.Background(), &corev1.Pod{}, devboxv1alpha2.PodNodeNameIndex, func(rawObj client.Object) []string {
|
||||
pod, _ := rawObj.(*corev1.Pod)
|
||||
if pod.Spec.NodeName == "" {
|
||||
return nil
|
||||
}
|
||||
return []string{pod.Spec.NodeName}
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to index field %s: %w", devboxv1alpha2.PodNodeNameIndex, err)
|
||||
}
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
WithOptions(controller.Options{MaxConcurrentReconciles: 10}).
|
||||
For(&devboxv1alpha2.Devbox{}, builder.WithPredicates(predicate.Or(
|
||||
predicate.GenerationChangedPredicate{}, // enqueue request if devbox spec is updated
|
||||
NetworkTypeChangedPredicate{}, // enqueue request if devbox status.network.type is updated
|
||||
ContentIDChangedPredicate{}, // enqueue request if devbox status.contentID is updated
|
||||
LastContainerStatusChangedPredicate{}, // enqueue request if devbox status.lastContainerStatus is updated
|
||||
PhaseChangedPredicate{}, // enqueue request if devbox status.phase is updated or status.phase is `Error`
|
||||
))).
|
||||
Owns(&corev1.Pod{}, builder.WithPredicates(predicate.ResourceVersionChangedPredicate{})).
|
||||
// enqueue request if pod spec/status is updated
|
||||
Owns(&corev1.Service{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
|
||||
// enqueue request if service spec is updated
|
||||
Owns(&corev1.Secret{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
|
||||
Complete(r)
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
devboxv1alpha2 "github.com/labring/sealos/controllers/devbox/api/v1alpha2"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
)
|
||||
|
||||
var _ = Describe("Devbox Controller", func() {
|
||||
Context("When reconciling a resource", func() {
|
||||
const resourceName = "test-resource"
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
typeNamespacedName := types.NamespacedName{
|
||||
Name: resourceName,
|
||||
Namespace: "default", // TODO(user):Modify as needed
|
||||
}
|
||||
devbox := &devboxv1alpha2.Devbox{}
|
||||
|
||||
BeforeEach(func() {
|
||||
By("creating the custom resource for the Kind Devbox")
|
||||
err := k8sClient.Get(ctx, typeNamespacedName, devbox)
|
||||
if err != nil && errors.IsNotFound(err) {
|
||||
resource := &devboxv1alpha2.Devbox{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: resourceName,
|
||||
Namespace: "default",
|
||||
},
|
||||
// TODO(user): Specify other spec details if needed.
|
||||
}
|
||||
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
|
||||
}
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
// TODO(user): Cleanup logic after each test, like removing the resource instance.
|
||||
resource := &devboxv1alpha2.Devbox{}
|
||||
err := k8sClient.Get(ctx, typeNamespacedName, resource)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Cleanup the specific resource instance Devbox")
|
||||
Expect(k8sClient.Delete(ctx, resource)).To(Succeed())
|
||||
})
|
||||
It("should successfully reconcile the resource", func() {
|
||||
By("Reconciling the created resource")
|
||||
controllerReconciler := &DevboxReconciler{
|
||||
Client: k8sClient,
|
||||
Scheme: k8sClient.Scheme(),
|
||||
}
|
||||
|
||||
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
|
||||
NamespacedName: typeNamespacedName,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.
|
||||
// Example: If you expect a certain status condition after reconciliation, verify it here.
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,337 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
devboxv1alpha2 "github.com/labring/sealos/controllers/devbox/api/v1alpha2"
|
||||
"github.com/labring/sealos/controllers/devbox/internal/controller/helper"
|
||||
"github.com/labring/sealos/controllers/devbox/internal/stat"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
||||
func (r *DevboxReconciler) getAcceptanceConsideration(
|
||||
ctx context.Context,
|
||||
) (helper.AcceptanceConsideration, error) {
|
||||
logger := log.FromContext(ctx)
|
||||
node := &corev1.Node{}
|
||||
if err := r.Get(context.Background(), client.ObjectKey{Name: r.NodeName}, node); err != nil {
|
||||
return helper.AcceptanceConsideration{}, err
|
||||
}
|
||||
ann := node.Annotations
|
||||
ac := helper.AcceptanceConsideration{}
|
||||
if v, err := strconv.ParseFloat(
|
||||
ann[devboxv1alpha2.AnnotationContainerFSAvailableThreshold],
|
||||
64,
|
||||
); err != nil {
|
||||
logger.Info(
|
||||
"failed to parse containerfs available threshold. use default value instead",
|
||||
"value",
|
||||
ann[devboxv1alpha2.AnnotationContainerFSAvailableThreshold],
|
||||
)
|
||||
ac.ContainerFSAvailableThreshold = helper.DefaultContainerFSAvailableThreshold
|
||||
} else {
|
||||
ac.ContainerFSAvailableThreshold = v
|
||||
}
|
||||
if v, err := strconv.ParseFloat(ann[devboxv1alpha2.AnnotationCPURequestRatio], 64); err != nil {
|
||||
logger.Info(
|
||||
"failed to parse CPU request ratio. use default value instead",
|
||||
"value",
|
||||
ann[devboxv1alpha2.AnnotationCPURequestRatio],
|
||||
)
|
||||
ac.CPURequestRatio = helper.DefaultCPURequestRatio
|
||||
} else {
|
||||
ac.CPURequestRatio = v
|
||||
}
|
||||
if v, err := strconv.ParseFloat(ann[devboxv1alpha2.AnnotationCPULimitRatio], 64); err != nil {
|
||||
logger.Info(
|
||||
"failed to parse CPU limit ratio. use default value instead",
|
||||
"value",
|
||||
ann[devboxv1alpha2.AnnotationCPULimitRatio],
|
||||
)
|
||||
ac.CPULimitRatio = helper.DefaultCPULimitRatio
|
||||
} else {
|
||||
ac.CPULimitRatio = v
|
||||
}
|
||||
if v, err := strconv.ParseFloat(
|
||||
ann[devboxv1alpha2.AnnotationMemoryRequestRatio],
|
||||
64,
|
||||
); err != nil {
|
||||
logger.Info(
|
||||
"failed to parse memory request ratio. use default value instead",
|
||||
"value",
|
||||
ann[devboxv1alpha2.AnnotationMemoryRequestRatio],
|
||||
)
|
||||
ac.MemoryRequestRatio = helper.DefaultMemoryRequestRatio
|
||||
} else {
|
||||
ac.MemoryRequestRatio = v
|
||||
}
|
||||
if v, err := strconv.ParseFloat(
|
||||
ann[devboxv1alpha2.AnnotationMemoryLimitRatio],
|
||||
64,
|
||||
); err != nil {
|
||||
logger.Info(
|
||||
"failed to parse memory limit ratio. use default value instead",
|
||||
"value",
|
||||
ann[devboxv1alpha2.AnnotationMemoryLimitRatio],
|
||||
)
|
||||
ac.MemoryLimitRatio = helper.DefaultMemoryLimitRatio
|
||||
} else {
|
||||
ac.MemoryLimitRatio = v
|
||||
}
|
||||
return ac, nil
|
||||
}
|
||||
|
||||
func (r *DevboxReconciler) getAcceptanceScore(
|
||||
ctx context.Context,
|
||||
devbox *devboxv1alpha2.Devbox,
|
||||
) int {
|
||||
logger := log.FromContext(ctx)
|
||||
var (
|
||||
ac helper.AcceptanceConsideration
|
||||
containerFsStats stat.FsStats
|
||||
err error
|
||||
availableBytes uint64
|
||||
availablePercentage float64
|
||||
capacityBytes uint64
|
||||
cpuRequestRatio float64
|
||||
cpuLimitRatio float64
|
||||
memoryRequestRatio float64
|
||||
memoryLimitRatio float64
|
||||
storageLimitBytes int64
|
||||
|
||||
score int
|
||||
)
|
||||
ac, err = r.getAcceptanceConsideration(ctx)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to get acceptance consideration")
|
||||
goto unsuitable // If we can't get the acceptance consideration, we assume the node is not suitable
|
||||
}
|
||||
containerFsStats, err = r.ContainerFsStats(ctx)
|
||||
switch {
|
||||
case err != nil:
|
||||
logger.Error(err, "failed to get container filesystem stats")
|
||||
goto unsuitable // If we can't get the container filesystem stats, we assume the node is not suitable
|
||||
case containerFsStats.AvailableBytes == nil:
|
||||
logger.Info("available bytes is nil, assume the node is not suitable")
|
||||
goto unsuitable // If we can't get the available bytes, we assume the node is not suitable
|
||||
case containerFsStats.CapacityBytes == nil:
|
||||
logger.Info("capacity bytes is nil, assume the node is not suitable")
|
||||
goto unsuitable // If we can't get the capacity bytes, we assume the node is not suitable
|
||||
}
|
||||
availableBytes = *containerFsStats.AvailableBytes
|
||||
capacityBytes = *containerFsStats.CapacityBytes
|
||||
if storageLimitBytes, err = helper.GetStorageLimitInBytes(devbox); err != nil {
|
||||
logger.Error(err, "failed to get storage limit")
|
||||
goto unsuitable // If we can't get the storage limit, we assume the node is not suitable
|
||||
} else if storageLimitBytes > 0 && availableBytes < uint64(storageLimitBytes) {
|
||||
logger.Info(
|
||||
"available bytes less than storage limit",
|
||||
"availableBytes",
|
||||
availableBytes,
|
||||
"storageLimitBytes",
|
||||
storageLimitBytes,
|
||||
)
|
||||
goto unsuitable // If available bytes are less than the storage limit, we assume the node is not suitable
|
||||
}
|
||||
availablePercentage = float64(availableBytes) / float64(capacityBytes) * 100
|
||||
if availablePercentage > ac.ContainerFSAvailableThreshold {
|
||||
logger.Info("container filesystem available percentage is greater than threshold",
|
||||
"availablePercentage", availablePercentage,
|
||||
"threshold", ac.ContainerFSAvailableThreshold)
|
||||
score += getScoreUnit(1)
|
||||
}
|
||||
cpuRequestRatio, err = r.getTotalCPURequestRatio(ctx)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to get total CPU request")
|
||||
goto unsuitable // If we can't get the CPU request, we assume the node is not suitable
|
||||
} else if cpuRequestRatio < ac.CPURequestRatio {
|
||||
logger.Info(
|
||||
"cpu request ratio is less than cpu overcommitment request ratio",
|
||||
"RequestRatio",
|
||||
cpuRequestRatio,
|
||||
"ratio",
|
||||
ac.CPURequestRatio,
|
||||
)
|
||||
score += getScoreUnit(0)
|
||||
}
|
||||
cpuLimitRatio, err = r.getTotalCPULimitRatio(ctx)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to get total CPU limit")
|
||||
goto unsuitable // If we can't get the CPU limit, we assume the node is not suitable
|
||||
} else if cpuLimitRatio < ac.CPULimitRatio {
|
||||
logger.Info(
|
||||
"cpu limit ratio is less than cpu overcommitment limit ratio",
|
||||
"LimitRatio",
|
||||
cpuLimitRatio,
|
||||
"ratio",
|
||||
ac.CPULimitRatio,
|
||||
)
|
||||
score += getScoreUnit(0)
|
||||
}
|
||||
memoryRequestRatio, err = r.getTotalMemoryRequestRatio(ctx)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to get total memory request")
|
||||
goto unsuitable // If we can't get the memory request, we assume the node is not suitable
|
||||
} else if memoryRequestRatio < ac.MemoryRequestRatio {
|
||||
logger.Info(
|
||||
"memory request ratio is less than memory overcommitment request ratio",
|
||||
"RequestRatio",
|
||||
memoryRequestRatio,
|
||||
"ratio",
|
||||
ac.MemoryRequestRatio,
|
||||
)
|
||||
score += getScoreUnit(0)
|
||||
}
|
||||
memoryLimitRatio, err = r.getTotalMemoryLimitRatio(ctx)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to get total memory limit")
|
||||
goto unsuitable // If we can't get the memory limit, we assume the node is not suitable
|
||||
} else if memoryLimitRatio < ac.MemoryLimitRatio {
|
||||
logger.Info(
|
||||
"memory limit ratio is less than memory overcommitment limit ratio",
|
||||
"LimitRatio",
|
||||
memoryLimitRatio,
|
||||
"ratio",
|
||||
ac.MemoryLimitRatio,
|
||||
)
|
||||
score += getScoreUnit(0)
|
||||
}
|
||||
return score
|
||||
unsuitable:
|
||||
return math.MinInt
|
||||
}
|
||||
|
||||
// This function may lead to overflow if p is too large, but since p is always in the range of 0-6, it should be safe.
|
||||
// Use with caution.
|
||||
func getScoreUnit(p uint) int {
|
||||
return 16 << (p * 4)
|
||||
}
|
||||
|
||||
// getTotalCPURequestRatio returns the total CPU requests (in millicores) ratio for all pods in the namespace.
|
||||
func (r *DevboxReconciler) getTotalCPURequestRatio(ctx context.Context) (float64, error) {
|
||||
podList := &corev1.PodList{}
|
||||
listOpts := []client.ListOption{
|
||||
client.MatchingFields{devboxv1alpha2.PodNodeNameIndex: r.NodeName},
|
||||
}
|
||||
if err := r.List(ctx, podList, listOpts...); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var totalCPURequest int64
|
||||
for _, pod := range podList.Items {
|
||||
for _, container := range pod.Spec.Containers {
|
||||
if cpuReq, ok := container.Resources.Requests[corev1.ResourceCPU]; ok {
|
||||
// TODO: check if this could lead to overflow
|
||||
totalCPURequest += cpuReq.Value()
|
||||
}
|
||||
}
|
||||
}
|
||||
node := &corev1.Node{}
|
||||
if err := r.Get(ctx, client.ObjectKey{Name: r.NodeName}, node); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
allocatableCPU := node.Status.Allocatable[corev1.ResourceCPU]
|
||||
allocatableMilli := allocatableCPU.Value()
|
||||
if allocatableMilli == 0 {
|
||||
return 0, fmt.Errorf("node %s allocatable CPU is zero", r.NodeName)
|
||||
}
|
||||
ratio := float64(totalCPURequest) / float64(allocatableMilli)
|
||||
return ratio, nil
|
||||
}
|
||||
|
||||
// getTotalCPULimitRatio returns the total CPU limits (in millicores) ratio for all pods in the namespace.
|
||||
func (r *DevboxReconciler) getTotalCPULimitRatio(ctx context.Context) (float64, error) {
|
||||
podList := &corev1.PodList{}
|
||||
listOpts := []client.ListOption{
|
||||
client.MatchingFields{devboxv1alpha2.PodNodeNameIndex: r.NodeName},
|
||||
}
|
||||
if err := r.List(ctx, podList, listOpts...); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var totalCPULimit int64
|
||||
for _, pod := range podList.Items {
|
||||
for _, container := range pod.Spec.Containers {
|
||||
if cpuLimit, ok := container.Resources.Limits[corev1.ResourceCPU]; ok {
|
||||
// TODO: check if this could lead to overflow
|
||||
totalCPULimit += cpuLimit.Value()
|
||||
}
|
||||
}
|
||||
}
|
||||
node := &corev1.Node{}
|
||||
if err := r.Get(ctx, client.ObjectKey{Name: r.NodeName}, node); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
allocatableCPU := node.Status.Allocatable[corev1.ResourceCPU]
|
||||
allocatableMilli := allocatableCPU.Value()
|
||||
if allocatableMilli == 0 {
|
||||
return 0, fmt.Errorf("node %s allocatable CPU is zero", r.NodeName)
|
||||
}
|
||||
ratio := float64(totalCPULimit) / float64(allocatableMilli)
|
||||
return ratio, nil
|
||||
}
|
||||
|
||||
// getTotalMemoryRequestRatio returns the total memory requests ratio for all pods in the namespace.
|
||||
func (r *DevboxReconciler) getTotalMemoryRequestRatio(ctx context.Context) (float64, error) {
|
||||
podList := &corev1.PodList{}
|
||||
listOpts := []client.ListOption{
|
||||
client.MatchingFields{devboxv1alpha2.PodNodeNameIndex: r.NodeName},
|
||||
}
|
||||
if err := r.List(ctx, podList, listOpts...); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var totalMemoryRequest int64
|
||||
for _, pod := range podList.Items {
|
||||
for _, container := range pod.Spec.Containers {
|
||||
if memReq, ok := container.Resources.Requests[corev1.ResourceMemory]; ok {
|
||||
// TODO: check if this could lead to overflow
|
||||
totalMemoryRequest += memReq.Value()
|
||||
}
|
||||
}
|
||||
}
|
||||
node := &corev1.Node{}
|
||||
if err := r.Get(ctx, client.ObjectKey{Name: r.NodeName}, node); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
allocatableMemory := node.Status.Allocatable[corev1.ResourceMemory]
|
||||
allocatableBytes := allocatableMemory.Value()
|
||||
if allocatableBytes == 0 {
|
||||
return 0, fmt.Errorf("node %s allocatable memory is zero", r.NodeName)
|
||||
}
|
||||
ratio := float64(totalMemoryRequest) / float64(allocatableBytes)
|
||||
return ratio, nil
|
||||
}
|
||||
|
||||
// getTotalMemoryLimitRatio returns the total memory limits ratio for all pods in the namespace.
|
||||
func (r *DevboxReconciler) getTotalMemoryLimitRatio(ctx context.Context) (float64, error) {
|
||||
podList := &corev1.PodList{}
|
||||
listOpts := []client.ListOption{
|
||||
client.MatchingFields{devboxv1alpha2.PodNodeNameIndex: r.NodeName},
|
||||
}
|
||||
if err := r.List(ctx, podList, listOpts...); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var totalMemoryLimit int64
|
||||
for _, pod := range podList.Items {
|
||||
for _, container := range pod.Spec.Containers {
|
||||
if memLimit, ok := container.Resources.Limits[corev1.ResourceMemory]; ok {
|
||||
// TODO: check if this could lead to overflow
|
||||
totalMemoryLimit += memLimit.Value()
|
||||
}
|
||||
}
|
||||
}
|
||||
node := &corev1.Node{}
|
||||
if err := r.Get(ctx, client.ObjectKey{Name: r.NodeName}, node); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
allocatableMemory := node.Status.Allocatable[corev1.ResourceMemory]
|
||||
allocatableBytes := allocatableMemory.Value()
|
||||
if allocatableBytes == 0 {
|
||||
return 0, fmt.Errorf("node %s allocatable memory is zero", r.NodeName)
|
||||
}
|
||||
ratio := float64(totalMemoryLimit) / float64(allocatableBytes)
|
||||
return ratio, nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,363 +0,0 @@
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
devboxv1alpha2 "github.com/labring/sealos/controllers/devbox/api/v1alpha2"
|
||||
"github.com/labring/sealos/controllers/devbox/internal/controller/utils/registry"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/util/retry"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
||||
// DevboxreleaseReconciler reconciles a Devboxrelease object
|
||||
type DevboxreleaseReconciler struct {
|
||||
client.Client
|
||||
registry.Registry
|
||||
Scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=devbox.sealos.io,resources=devboxreleases,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=devbox.sealos.io,resources=devboxreleases/status,verbs=get;update;patch
|
||||
// +kubebuilder:rbac:groups=devbox.sealos.io,resources=devboxreleases/finalizers,verbs=update
|
||||
|
||||
func (r *DevboxreleaseReconciler) Reconcile(
|
||||
ctx context.Context,
|
||||
req ctrl.Request,
|
||||
) (ctrl.Result, error) {
|
||||
logger := log.FromContext(ctx)
|
||||
devboxRelease := &devboxv1alpha2.DevBoxRelease{}
|
||||
if err := r.Get(ctx, req.NamespacedName, devboxRelease); err != nil {
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
|
||||
if devboxRelease.DeletionTimestamp.IsZero() {
|
||||
// Add finalizer with retry
|
||||
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
latestRelease := &devboxv1alpha2.DevBoxRelease{}
|
||||
if err := r.Get(ctx, req.NamespacedName, latestRelease); err != nil {
|
||||
return err
|
||||
}
|
||||
if controllerutil.AddFinalizer(latestRelease, devboxv1alpha2.FinalizerName) {
|
||||
return r.Update(ctx, latestRelease)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
} else {
|
||||
// Remove finalizer with retry
|
||||
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
latestRelease := &devboxv1alpha2.DevBoxRelease{}
|
||||
if err := r.Get(ctx, req.NamespacedName, latestRelease); err != nil {
|
||||
return client.IgnoreNotFound(err)
|
||||
}
|
||||
if controllerutil.RemoveFinalizer(latestRelease, devboxv1alpha2.FinalizerName) {
|
||||
return r.Update(ctx, latestRelease)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
logger.Info(
|
||||
"Reconciling DevBoxRelease",
|
||||
"devbox",
|
||||
devboxRelease.Spec.DevboxName,
|
||||
"version",
|
||||
devboxRelease.Spec.Version,
|
||||
"phase",
|
||||
devboxRelease.Status.Phase,
|
||||
)
|
||||
|
||||
devbox := &devboxv1alpha2.Devbox{}
|
||||
if err := r.Get(
|
||||
ctx,
|
||||
client.ObjectKey{Namespace: devboxRelease.Namespace, Name: devboxRelease.Spec.DevboxName},
|
||||
devbox,
|
||||
); err != nil {
|
||||
logger.Error(err, "Failed to get devbox", "devbox", devboxRelease.Spec.DevboxName)
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
// if devboxRelease.Status.Phase is success, skip release
|
||||
if devboxRelease.Status.Phase == devboxv1alpha2.DevBoxReleasePhaseSuccess {
|
||||
logger.Info(
|
||||
"DevBoxRelease is already released, skipping release",
|
||||
"devbox",
|
||||
devboxRelease.Spec.DevboxName,
|
||||
"devboxRelease",
|
||||
devboxRelease.Name,
|
||||
"version",
|
||||
devboxRelease.Spec.Version,
|
||||
)
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// if devbox is running, skip release
|
||||
if devbox.Status.State == devboxv1alpha2.DevboxStateRunning ||
|
||||
devbox.Status.State == devboxv1alpha2.DevboxStatePaused {
|
||||
logger.Info(
|
||||
"Devbox is running or paused, skipping release",
|
||||
"devbox",
|
||||
devboxRelease.Spec.DevboxName,
|
||||
"devboxRelease",
|
||||
devboxRelease.Name,
|
||||
"version",
|
||||
devboxRelease.Spec.Version,
|
||||
)
|
||||
return ctrl.Result{RequeueAfter: time.Second * 10}, nil
|
||||
}
|
||||
if devboxRelease.Status.Phase == "" {
|
||||
// Initialize release phase with retry
|
||||
sourceImage := devbox.Status.CommitRecords[devbox.Status.ContentID].BaseImage
|
||||
targetImage := fmt.Sprintf(
|
||||
"%s/%s/%s:%s",
|
||||
r.Host,
|
||||
devboxRelease.Namespace,
|
||||
devboxRelease.Spec.DevboxName,
|
||||
devboxRelease.Spec.Version,
|
||||
)
|
||||
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
latestRelease := &devboxv1alpha2.DevBoxRelease{}
|
||||
if err := r.Get(
|
||||
ctx,
|
||||
client.ObjectKeyFromObject(devboxRelease),
|
||||
latestRelease,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
latestRelease.Status.Phase = devboxv1alpha2.DevBoxReleasePhasePending
|
||||
latestRelease.Status.OriginalDevboxState = devbox.Spec.State
|
||||
latestRelease.Status.SourceImage = sourceImage
|
||||
latestRelease.Status.TargetImage = targetImage
|
||||
return r.Status().Update(ctx, latestRelease)
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error(
|
||||
err,
|
||||
"Failed to update status",
|
||||
"devbox",
|
||||
devboxRelease.Spec.DevboxName,
|
||||
"devboxRelease",
|
||||
devboxRelease.Name,
|
||||
"version",
|
||||
devboxRelease.Spec.Version,
|
||||
)
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{Requeue: true}, nil
|
||||
}
|
||||
if devboxRelease.Status.Phase == devboxv1alpha2.DevBoxReleasePhasePending {
|
||||
logger.Info(
|
||||
"Creating release tag",
|
||||
"devbox",
|
||||
devboxRelease.Spec.DevboxName,
|
||||
"devboxRelease",
|
||||
devboxRelease.Name,
|
||||
"version",
|
||||
devboxRelease.Spec.Version,
|
||||
)
|
||||
err := r.Release(ctx, devboxRelease)
|
||||
if err != nil && errors.Is(err, registry.ErrManifestNotFound) {
|
||||
logger.Info(
|
||||
"Manifest not found, retrying",
|
||||
"devbox",
|
||||
devboxRelease.Spec.DevboxName,
|
||||
"devboxRelease",
|
||||
devboxRelease.Name,
|
||||
"version",
|
||||
devboxRelease.Spec.Version,
|
||||
)
|
||||
return ctrl.Result{RequeueAfter: time.Second * 10}, nil
|
||||
} else if err != nil {
|
||||
logger.Error(
|
||||
err,
|
||||
"Failed to create release tag",
|
||||
"devbox",
|
||||
devboxRelease.Spec.DevboxName,
|
||||
"devboxRelease",
|
||||
devboxRelease.Name,
|
||||
"version",
|
||||
devboxRelease.Spec.Version,
|
||||
)
|
||||
// Update status to failed with retry
|
||||
_ = retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
latestRelease := &devboxv1alpha2.DevBoxRelease{}
|
||||
if err := r.Get(
|
||||
ctx,
|
||||
client.ObjectKeyFromObject(devboxRelease),
|
||||
latestRelease,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
latestRelease.Status.Phase = devboxv1alpha2.DevBoxReleasePhaseFailed
|
||||
return r.Status().Update(ctx, latestRelease)
|
||||
})
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
logger.Info(
|
||||
"Release tag created",
|
||||
"devbox",
|
||||
devboxRelease.Spec.DevboxName,
|
||||
"devboxRelease",
|
||||
devboxRelease.Name,
|
||||
"version",
|
||||
devboxRelease.Spec.Version,
|
||||
)
|
||||
// Update status to success with retry
|
||||
err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
latestRelease := &devboxv1alpha2.DevBoxRelease{}
|
||||
if err := r.Get(
|
||||
ctx,
|
||||
client.ObjectKeyFromObject(devboxRelease),
|
||||
latestRelease,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
latestRelease.Status.Phase = devboxv1alpha2.DevBoxReleasePhaseSuccess
|
||||
return r.Status().Update(ctx, latestRelease)
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error(
|
||||
err,
|
||||
"Failed to update status",
|
||||
"devbox",
|
||||
devboxRelease.Spec.DevboxName,
|
||||
"devboxRelease",
|
||||
devboxRelease.Name,
|
||||
"version",
|
||||
devboxRelease.Spec.Version,
|
||||
)
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
if devboxRelease.Spec.StartDevboxAfterRelease {
|
||||
err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
devbox := &devboxv1alpha2.Devbox{}
|
||||
if err := r.Get(
|
||||
ctx,
|
||||
client.ObjectKey{
|
||||
Namespace: devboxRelease.Namespace,
|
||||
Name: devboxRelease.Spec.DevboxName,
|
||||
},
|
||||
devbox,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Info(
|
||||
"Starting devbox after release",
|
||||
"devbox",
|
||||
devboxRelease.Spec.DevboxName,
|
||||
"devboxRelease",
|
||||
devboxRelease.Name,
|
||||
"version",
|
||||
devboxRelease.Spec.Version,
|
||||
)
|
||||
devbox.Spec.State = devboxv1alpha2.DevboxStateRunning
|
||||
if err = r.Update(ctx, devbox); err != nil {
|
||||
logger.Error(
|
||||
err,
|
||||
"Failed to update devbox",
|
||||
"devbox",
|
||||
devboxRelease.Spec.DevboxName,
|
||||
"devboxRelease",
|
||||
devboxRelease.Name,
|
||||
"version",
|
||||
devboxRelease.Spec.Version,
|
||||
)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error(
|
||||
err,
|
||||
"Failed to update devbox",
|
||||
"devbox",
|
||||
devboxRelease.Spec.DevboxName,
|
||||
"devboxRelease",
|
||||
devboxRelease.Name,
|
||||
"version",
|
||||
devboxRelease.Spec.Version,
|
||||
)
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.Info(
|
||||
"Reconciliation complete",
|
||||
"devbox",
|
||||
devboxRelease.Spec.DevboxName,
|
||||
"devboxRelease",
|
||||
devboxRelease.Name,
|
||||
"version",
|
||||
devboxRelease.Spec.Version,
|
||||
)
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
func (r *DevboxreleaseReconciler) Release(
|
||||
ctx context.Context,
|
||||
devboxRelease *devboxv1alpha2.DevBoxRelease,
|
||||
) error {
|
||||
logger := log.FromContext(ctx)
|
||||
if err := r.ReTag(
|
||||
devboxRelease.Status.SourceImage,
|
||||
devboxRelease.Status.TargetImage,
|
||||
); err != nil {
|
||||
logger.Error(
|
||||
err,
|
||||
"Failed to re-tag image",
|
||||
"devbox",
|
||||
devboxRelease.Spec.DevboxName,
|
||||
"devboxRelease",
|
||||
devboxRelease.Name,
|
||||
"version",
|
||||
devboxRelease.Spec.Version,
|
||||
)
|
||||
return err
|
||||
}
|
||||
logger.Info(
|
||||
"Image re-tagged",
|
||||
"devbox",
|
||||
devboxRelease.Spec.DevboxName,
|
||||
"devboxRelease",
|
||||
devboxRelease.Name,
|
||||
"version",
|
||||
devboxRelease.Spec.Version,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupWithManager sets up the controller with the Manager.
|
||||
func (r *DevboxreleaseReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&devboxv1alpha2.DevBoxRelease{}).
|
||||
Named("devboxrelease").
|
||||
Complete(r)
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
devboxv1alpha2 "github.com/labring/sealos/controllers/devbox/api/v1alpha2"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
)
|
||||
|
||||
var _ = Describe("Devboxrelease Controller", func() {
|
||||
Context("When reconciling a resource", func() {
|
||||
const resourceName = "test-resource"
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
typeNamespacedName := types.NamespacedName{
|
||||
Name: resourceName,
|
||||
Namespace: "default", // TODO(user):Modify as needed
|
||||
}
|
||||
devboxrelease := &devboxv1alpha2.DevBoxRelease{}
|
||||
|
||||
BeforeEach(func() {
|
||||
By("creating the custom resource for the Kind Devboxrelease")
|
||||
err := k8sClient.Get(ctx, typeNamespacedName, devboxrelease)
|
||||
if err != nil && errors.IsNotFound(err) {
|
||||
resource := &devboxv1alpha2.DevBoxRelease{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: resourceName,
|
||||
Namespace: "default",
|
||||
},
|
||||
// TODO(user): Specify other spec details if needed.
|
||||
}
|
||||
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
|
||||
}
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
// TODO(user): Cleanup logic after each test, like removing the resource instance.
|
||||
resource := &devboxv1alpha2.DevBoxRelease{}
|
||||
err := k8sClient.Get(ctx, typeNamespacedName, resource)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Cleanup the specific resource instance Devboxrelease")
|
||||
Expect(k8sClient.Delete(ctx, resource)).To(Succeed())
|
||||
})
|
||||
It("should successfully reconcile the resource", func() {
|
||||
By("Reconciling the created resource")
|
||||
controllerReconciler := &DevboxreleaseReconciler{
|
||||
Client: k8sClient,
|
||||
Scheme: k8sClient.Scheme(),
|
||||
}
|
||||
|
||||
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
|
||||
NamespacedName: typeNamespacedName,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.
|
||||
// Example: If you expect a certain status condition after reconciliation, verify it here.
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,706 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/google/uuid"
|
||||
devboxv1alpha2 "github.com/labring/sealos/controllers/devbox/api/v1alpha2"
|
||||
"github.com/labring/sealos/controllers/devbox/internal/commit"
|
||||
"github.com/labring/sealos/controllers/devbox/internal/controller/utils/events"
|
||||
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"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/rand"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
var (
|
||||
commitMap = sync.Map{}
|
||||
deleteMap = sync.Map{}
|
||||
)
|
||||
|
||||
type EventHandler struct {
|
||||
Committer commit.Committer
|
||||
CommitImageRegistry string
|
||||
NodeName string
|
||||
DefaultBaseImage string
|
||||
|
||||
Logger logr.Logger
|
||||
Client client.Client
|
||||
Scheme *runtime.Scheme
|
||||
Recorder record.EventRecorder
|
||||
}
|
||||
|
||||
// todo: handle state change event
|
||||
func (h *EventHandler) Handle(ctx context.Context, event *corev1.Event) error {
|
||||
h.Logger.Info("StateChangeHandler.Handle called",
|
||||
"event", event.Name,
|
||||
"eventSourceHost", event.Source.Host,
|
||||
"handlerNodeName", h.NodeName,
|
||||
"eventType", event.Type,
|
||||
"eventReason", event.Reason,
|
||||
"eventMessage", event.Message)
|
||||
|
||||
if event.Source.Host != h.NodeName {
|
||||
h.Logger.Info("event source host is not the node name, skip", "event", event)
|
||||
return nil
|
||||
}
|
||||
|
||||
switch event.Reason {
|
||||
// handle storage cleanup
|
||||
case events.ReasonStorageCleanupRequested:
|
||||
return h.handleStorageCleanup(ctx, event)
|
||||
|
||||
// handle state change
|
||||
case events.ReasonDevboxStateChanged:
|
||||
return h.handleDevboxStateChange(ctx, event)
|
||||
|
||||
default:
|
||||
return errors.New("invalid event")
|
||||
}
|
||||
}
|
||||
|
||||
// handleDevboxStateChange handle new structured state change event
|
||||
func (h *EventHandler) handleDevboxStateChange(ctx context.Context, event *corev1.Event) error {
|
||||
h.Logger.Info(
|
||||
"Devbox state change event detected",
|
||||
"event",
|
||||
event.Name,
|
||||
"message",
|
||||
event.Message,
|
||||
)
|
||||
devbox := &devboxv1alpha2.Devbox{}
|
||||
if err := h.Client.Get(
|
||||
ctx,
|
||||
types.NamespacedName{Namespace: event.Namespace, Name: event.InvolvedObject.Name},
|
||||
devbox,
|
||||
); err != nil {
|
||||
h.Logger.Error(err, "failed to get devbox", "devbox", event.InvolvedObject.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if state transition is valid and handle accordingly
|
||||
currentState := devbox.Status.State
|
||||
targetState := devbox.Spec.State
|
||||
|
||||
// Handle invalid state transition
|
||||
if currentState == devboxv1alpha2.DevboxStateShutdown &&
|
||||
targetState == devboxv1alpha2.DevboxStateStopped {
|
||||
h.Recorder.Eventf(
|
||||
devbox,
|
||||
corev1.EventTypeWarning,
|
||||
"Shutdown state is not allowed to be changed to stopped state",
|
||||
"Shutdown state is not allowed to be changed to stopped state",
|
||||
)
|
||||
h.Logger.Error(
|
||||
errors.New("shutdown state is not allowed to be changed to stopped state"),
|
||||
"shutdown state is not allowed to be changed to stopped state",
|
||||
"devbox",
|
||||
devbox.Name,
|
||||
)
|
||||
return errors.New("shutdown state is not allowed to be changed to stopped state")
|
||||
}
|
||||
|
||||
// Handle state transitions that require commit, only running and paused devbox can be shutdown or stopped
|
||||
needsCommit := (targetState == devboxv1alpha2.DevboxStateShutdown || targetState == devboxv1alpha2.DevboxStateStopped) &&
|
||||
(currentState == devboxv1alpha2.DevboxStateRunning || currentState == devboxv1alpha2.DevboxStatePaused)
|
||||
|
||||
if needsCommit {
|
||||
// Keep the lock held across the whole retry loop to prevent concurrent commits during backoff windows.
|
||||
commitKey := devbox.Status.ContentID
|
||||
if commitKey == "" {
|
||||
err := errors.New("empty contentID, cannot start commit")
|
||||
h.Logger.Error(err, "invalid devbox for commit", "devbox", devbox.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if commit is already in progress to prevent duplicate requests
|
||||
if _, loaded := commitMap.LoadOrStore(commitKey, true); loaded {
|
||||
h.Logger.Info(
|
||||
"commit already in progress, skipping duplicate request",
|
||||
"devbox",
|
||||
devbox.Name,
|
||||
"contentID",
|
||||
commitKey,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
defer commitMap.Delete(commitKey)
|
||||
|
||||
start := time.Now()
|
||||
h.Logger.Info(
|
||||
"start commit devbox",
|
||||
"devbox",
|
||||
devbox.Name,
|
||||
"contentID",
|
||||
devbox.Status.ContentID,
|
||||
"time",
|
||||
start,
|
||||
)
|
||||
|
||||
// retry commit devbox with retry logic
|
||||
// backoff: fixed 10s, up to 30 steps (~5min)
|
||||
err := retry.OnError(wait.Backoff{
|
||||
Duration: 10 * time.Second,
|
||||
Factor: 1.0,
|
||||
Jitter: 0.1,
|
||||
Steps: 30,
|
||||
}, func(err error) bool {
|
||||
// Don't retry if the context is cancelled/timed out, or if devbox is not found
|
||||
// Controller will handle storage cleanup when devbox is not found
|
||||
return !errors.Is(err, context.Canceled) &&
|
||||
!errors.Is(err, context.DeadlineExceeded) &&
|
||||
!apierrors.IsNotFound(err)
|
||||
}, func() error {
|
||||
err := h.commitDevbox(ctx, devbox, targetState)
|
||||
if err != nil {
|
||||
h.Logger.Error(err, "failed to commit devbox in retry", "devbox", devbox.Name)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
h.Logger.Error(err, "failed to commit devbox after retries", "devbox", devbox.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
h.Logger.Info(
|
||||
"commit devbox success",
|
||||
"devbox",
|
||||
devbox.Name,
|
||||
"contentID",
|
||||
devbox.Status.ContentID,
|
||||
"time",
|
||||
time.Since(start),
|
||||
)
|
||||
} else if currentState != targetState {
|
||||
// Handle simple state transitions without commit with retry
|
||||
h.Logger.Info(
|
||||
"update devbox status",
|
||||
"devbox",
|
||||
devbox.Name,
|
||||
"from",
|
||||
currentState,
|
||||
"to",
|
||||
targetState,
|
||||
)
|
||||
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
latestDevbox := &devboxv1alpha2.Devbox{}
|
||||
if err := h.Client.Get(
|
||||
ctx,
|
||||
types.NamespacedName{Namespace: devbox.Namespace, Name: devbox.Name},
|
||||
latestDevbox,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
latestDevbox.Status.State = targetState
|
||||
// Transition synced; clear pending and advance observedGeneration.
|
||||
if latestDevbox.Spec.State == latestDevbox.Status.State {
|
||||
latestDevbox.Status.ObservedGeneration = latestDevbox.Generation
|
||||
latestDevbox.SetCondition(metav1.Condition{
|
||||
Type: devboxv1alpha2.DevboxConditionStateTransitionPending,
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: latestDevbox.Generation,
|
||||
Reason: devboxv1alpha2.DevboxReasonStateTransitionSynced,
|
||||
Message: "spec.state matches status.state",
|
||||
LastTransitionTime: metav1.Now(),
|
||||
})
|
||||
}
|
||||
latestDevbox.SetCondition(metav1.Condition{
|
||||
Type: devboxv1alpha2.DevboxConditionCommitInProgress,
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: latestDevbox.Generation,
|
||||
Reason: devboxv1alpha2.DevboxReasonCommitNotInProgress,
|
||||
Message: "no commit workflow in progress",
|
||||
LastTransitionTime: metav1.Now(),
|
||||
})
|
||||
return h.Client.Status().Update(ctx, latestDevbox)
|
||||
})
|
||||
if err != nil {
|
||||
h.Logger.Error(err, "failed to update devbox status", "devbox", devbox.Name)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *EventHandler) handleStorageCleanup(ctx context.Context, event *corev1.Event) error {
|
||||
h.Logger.Info("Storage cleanup event detected", "event", event.Name, "message", event.Message)
|
||||
if _, loaded := deleteMap.LoadOrStore(event.InvolvedObject.Name, true); loaded {
|
||||
h.Logger.Info(
|
||||
"delete devbox already in progress, skipping duplicate request",
|
||||
"devbox",
|
||||
event.InvolvedObject.Name,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
defer func() {
|
||||
deleteMap.Delete(event.InvolvedObject.Name)
|
||||
}()
|
||||
if err := h.removeStorage(ctx, event); err != nil {
|
||||
h.Logger.Error(err, "failed to clean up storage during delete devbox", "devbox", event.Name)
|
||||
h.Recorder.Eventf(&corev1.ObjectReference{
|
||||
Kind: event.InvolvedObject.Kind,
|
||||
Name: event.InvolvedObject.Name,
|
||||
Namespace: event.InvolvedObject.Namespace,
|
||||
}, corev1.EventTypeWarning, "Storage cleanup failed",
|
||||
"Failed to cleanup Storage: %v", err)
|
||||
} else {
|
||||
h.Logger.Info("Successfully cleaned up storage during deletion", "devbox", event.Name)
|
||||
h.Recorder.Eventf(&corev1.ObjectReference{
|
||||
Kind: event.InvolvedObject.Kind,
|
||||
Name: event.InvolvedObject.Name,
|
||||
Namespace: event.InvolvedObject.Namespace,
|
||||
}, corev1.EventTypeNormal, "Storage cleanup succeeded",
|
||||
"Successfully cleaned up storage for devbox %s", event.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *EventHandler) commitDevbox(
|
||||
ctx context.Context,
|
||||
devbox *devboxv1alpha2.Devbox,
|
||||
targetState devboxv1alpha2.DevboxState,
|
||||
) error {
|
||||
if err := h.Client.Get(
|
||||
ctx,
|
||||
types.NamespacedName{Namespace: devbox.Namespace, Name: devbox.Name},
|
||||
devbox,
|
||||
); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
h.Logger.Info("devbox not found at start of commit", "devbox", devbox.Name)
|
||||
return err
|
||||
}
|
||||
h.Logger.Error(err, "failed to get devbox", "devbox", devbox.Name)
|
||||
return err
|
||||
}
|
||||
// do commit, update devbox commit record, update devbox status state to shutdown, add a new commit record for the new content id
|
||||
// step 0: set commit status to committing to prevent duplicate requests with retry
|
||||
if err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
latestDevbox := &devboxv1alpha2.Devbox{}
|
||||
if err := h.Client.Get(
|
||||
ctx,
|
||||
types.NamespacedName{Namespace: devbox.Namespace, Name: devbox.Name},
|
||||
latestDevbox,
|
||||
); err != nil {
|
||||
// If devbox is not found, return the error to stop retrying
|
||||
if apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
latestDevbox.Status.CommitRecords[latestDevbox.Status.ContentID].CommitStatus = devboxv1alpha2.CommitStatusCommitting
|
||||
latestDevbox.Status.CommitRecords[latestDevbox.Status.ContentID].UpdateTime = metav1.Now()
|
||||
latestDevbox.SetCondition(metav1.Condition{
|
||||
Type: devboxv1alpha2.DevboxConditionCommitInProgress,
|
||||
Status: metav1.ConditionTrue,
|
||||
ObservedGeneration: latestDevbox.Generation,
|
||||
Reason: devboxv1alpha2.DevboxReasonCommitStarted,
|
||||
Message: "commit workflow in progress",
|
||||
LastTransitionTime: metav1.Now(),
|
||||
})
|
||||
return h.Client.Status().Update(ctx, latestDevbox)
|
||||
}); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
h.Logger.Info("devbox not found when setting commit status", "devbox", devbox.Name)
|
||||
return err
|
||||
}
|
||||
h.Logger.Error(err, "failed to update commit status to committing", "devbox", devbox.Name)
|
||||
return err
|
||||
}
|
||||
h.Logger.Info(
|
||||
"set commit status to committing",
|
||||
"devbox",
|
||||
devbox.Name,
|
||||
"contentID",
|
||||
devbox.Status.ContentID,
|
||||
)
|
||||
|
||||
if err := h.Client.Get(
|
||||
ctx,
|
||||
types.NamespacedName{Namespace: devbox.Namespace, Name: devbox.Name},
|
||||
devbox,
|
||||
); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
h.Logger.Info("devbox not found before commit", "devbox", devbox.Name)
|
||||
return err
|
||||
}
|
||||
h.Logger.Error(err, "failed to get devbox", "devbox", devbox.Name)
|
||||
return err
|
||||
}
|
||||
// step 1: do commit, push image, remove container whether commit success or not
|
||||
baseImage := devbox.Status.CommitRecords[devbox.Status.ContentID].BaseImage
|
||||
commitImage := devbox.Status.CommitRecords[devbox.Status.ContentID].CommitImage
|
||||
oldContentID := devbox.Status.ContentID
|
||||
h.Logger.Info(
|
||||
"commit devbox",
|
||||
"devbox",
|
||||
devbox.Name,
|
||||
"baseImage",
|
||||
baseImage,
|
||||
"commitImage",
|
||||
commitImage,
|
||||
)
|
||||
var containerID string
|
||||
var commitErr error
|
||||
removeImageNames := make([]string, 0, 2)
|
||||
defer func() {
|
||||
// remove container whether commit success or not
|
||||
if err := h.Committer.RemoveContainers(ctx, []string{containerID}); err != nil {
|
||||
h.Logger.Error(err, "failed to remove container", "containerID", containerID)
|
||||
}
|
||||
// remove after push image whether push success
|
||||
if len(removeImageNames) > 0 {
|
||||
if err := h.Committer.RemoveImages(
|
||||
ctx,
|
||||
removeImageNames,
|
||||
commit.DefaultRemoveImageForce,
|
||||
commit.DefaultRemoveImageAsync,
|
||||
); err != nil {
|
||||
h.Logger.Error(err, "failed to remove image", "removeImageNames", removeImageNames)
|
||||
}
|
||||
}
|
||||
}()
|
||||
if containerID, commitErr = h.Committer.Commit(
|
||||
ctx,
|
||||
devbox.Name,
|
||||
devbox.Status.ContentID,
|
||||
baseImage,
|
||||
commitImage,
|
||||
); commitErr != nil {
|
||||
h.Logger.Error(commitErr, "failed to commit devbox", "devbox", devbox.Name)
|
||||
// Update commit status to failed on commit error with retry
|
||||
updateErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
latestDevbox := &devboxv1alpha2.Devbox{}
|
||||
if err := h.Client.Get(
|
||||
ctx,
|
||||
types.NamespacedName{Namespace: devbox.Namespace, Name: devbox.Name},
|
||||
latestDevbox,
|
||||
); err != nil {
|
||||
// If devbox is not found, return the error
|
||||
// RetryOnConflict will return this error immediately without retrying
|
||||
if apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
latestDevbox.Status.CommitRecords[latestDevbox.Status.ContentID].CommitStatus = devboxv1alpha2.CommitStatusFailed
|
||||
latestDevbox.Status.CommitRecords[latestDevbox.Status.ContentID].UpdateTime = metav1.Now()
|
||||
latestDevbox.SetCondition(metav1.Condition{
|
||||
Type: devboxv1alpha2.DevboxConditionCommitInProgress,
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: latestDevbox.Generation,
|
||||
Reason: devboxv1alpha2.DevboxReasonCommitFailed,
|
||||
Message: "commit workflow failed",
|
||||
LastTransitionTime: metav1.Now(),
|
||||
})
|
||||
return h.Client.Status().Update(ctx, latestDevbox)
|
||||
})
|
||||
if updateErr != nil {
|
||||
if apierrors.IsNotFound(updateErr) {
|
||||
h.Logger.Info(
|
||||
"devbox not found when updating commit status to failed",
|
||||
"devbox",
|
||||
devbox.Name,
|
||||
)
|
||||
return updateErr
|
||||
}
|
||||
h.Logger.Error(
|
||||
updateErr,
|
||||
"failed to update commit status to failed",
|
||||
"devbox",
|
||||
devbox.Name,
|
||||
)
|
||||
}
|
||||
return commitErr
|
||||
}
|
||||
if err := h.Client.Get(
|
||||
ctx,
|
||||
types.NamespacedName{Namespace: devbox.Namespace, Name: devbox.Name},
|
||||
devbox,
|
||||
); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
h.Logger.Info("devbox not found before push", "devbox", devbox.Name)
|
||||
return err
|
||||
}
|
||||
h.Logger.Error(err, "failed to get devbox", "devbox", devbox.Name)
|
||||
return err
|
||||
}
|
||||
if err := h.Committer.Push(ctx, commitImage); err != nil {
|
||||
h.Logger.Error(err, "failed to push commit image", "commitImage", commitImage)
|
||||
// Update commit status to failed on push error with retry
|
||||
updateErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
latestDevbox := &devboxv1alpha2.Devbox{}
|
||||
if err := h.Client.Get(
|
||||
ctx,
|
||||
types.NamespacedName{Namespace: devbox.Namespace, Name: devbox.Name},
|
||||
latestDevbox,
|
||||
); err != nil {
|
||||
// If devbox is not found, return the error
|
||||
if apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
latestDevbox.Status.CommitRecords[latestDevbox.Status.ContentID].CommitStatus = devboxv1alpha2.CommitStatusFailed
|
||||
latestDevbox.Status.CommitRecords[latestDevbox.Status.ContentID].UpdateTime = metav1.Now()
|
||||
latestDevbox.SetCondition(metav1.Condition{
|
||||
Type: devboxv1alpha2.DevboxConditionCommitInProgress,
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: latestDevbox.Generation,
|
||||
Reason: devboxv1alpha2.DevboxReasonCommitFailed,
|
||||
Message: "commit workflow failed (push error)",
|
||||
LastTransitionTime: metav1.Now(),
|
||||
})
|
||||
return h.Client.Status().Update(ctx, latestDevbox)
|
||||
})
|
||||
if updateErr != nil {
|
||||
if apierrors.IsNotFound(updateErr) {
|
||||
h.Logger.Info(
|
||||
"devbox not found when updating commit status to failed after push error",
|
||||
"devbox",
|
||||
devbox.Name,
|
||||
)
|
||||
return updateErr
|
||||
}
|
||||
h.Logger.Error(
|
||||
updateErr,
|
||||
"failed to update commit status to failed",
|
||||
"devbox",
|
||||
devbox.Name,
|
||||
)
|
||||
}
|
||||
return err
|
||||
}
|
||||
removeImageNames = append(removeImageNames, commitImage, baseImage)
|
||||
// step 2: update devbox commit record
|
||||
// step 3: update devbox status state to shutdown
|
||||
// step 4: add a new commit record for the new content id
|
||||
// make sure that always have a new commit record for shutdown state
|
||||
newContentID := uuid.New().String()
|
||||
newCommitImage := h.generateImageName(devbox)
|
||||
h.Logger.Info("update devbox status to shutdown", "devbox", devbox.Name)
|
||||
if err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
latestDevbox := &devboxv1alpha2.Devbox{}
|
||||
if err := h.Client.Get(
|
||||
ctx,
|
||||
types.NamespacedName{Namespace: devbox.Namespace, Name: devbox.Name},
|
||||
latestDevbox,
|
||||
); err != nil {
|
||||
// If devbox is not found, return the error to stop retrying
|
||||
if apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
latestDevbox.Status.CommitRecords[latestDevbox.Status.ContentID].CommitStatus = devboxv1alpha2.CommitStatusSuccess
|
||||
latestDevbox.Status.CommitRecords[latestDevbox.Status.ContentID].CommitTime = metav1.Now()
|
||||
latestDevbox.Status.State = targetState
|
||||
latestDevbox.Status.ContentID = newContentID
|
||||
latestDevbox.Status.CommitRecords[newContentID] = &devboxv1alpha2.CommitRecord{
|
||||
CommitStatus: devboxv1alpha2.CommitStatusPending,
|
||||
Node: "",
|
||||
BaseImage: commitImage,
|
||||
CommitImage: newCommitImage,
|
||||
GenerateTime: metav1.Now(),
|
||||
}
|
||||
latestDevbox.Status.Node = ""
|
||||
// Commit succeeded; clear in-progress, and clear pending transition if synced.
|
||||
latestDevbox.SetCondition(metav1.Condition{
|
||||
Type: devboxv1alpha2.DevboxConditionCommitInProgress,
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: latestDevbox.Generation,
|
||||
Reason: devboxv1alpha2.DevboxReasonCommitSucceeded,
|
||||
Message: "commit workflow succeeded",
|
||||
LastTransitionTime: metav1.Now(),
|
||||
})
|
||||
if latestDevbox.Spec.State == latestDevbox.Status.State {
|
||||
latestDevbox.Status.ObservedGeneration = latestDevbox.Generation
|
||||
latestDevbox.SetCondition(metav1.Condition{
|
||||
Type: devboxv1alpha2.DevboxConditionStateTransitionPending,
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: latestDevbox.Generation,
|
||||
Reason: devboxv1alpha2.DevboxReasonStateTransitionSynced,
|
||||
Message: "spec.state matches status.state",
|
||||
LastTransitionTime: metav1.Now(),
|
||||
})
|
||||
}
|
||||
return h.Client.Status().Update(ctx, latestDevbox)
|
||||
}); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
h.Logger.Info("devbox not found when updating status", "devbox", devbox.Name)
|
||||
return err
|
||||
}
|
||||
h.Logger.Error(err, "failed to update devbox status", "devbox", devbox.Name)
|
||||
return err
|
||||
}
|
||||
// step 5: set LV removable
|
||||
if err := h.Committer.SetLvRemovable(ctx, containerID, oldContentID); err != nil {
|
||||
h.Logger.Error(
|
||||
err,
|
||||
"failed to set LV removable",
|
||||
"containerID",
|
||||
containerID,
|
||||
"contentID",
|
||||
oldContentID,
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *EventHandler) generateImageName(devbox *devboxv1alpha2.Devbox) string {
|
||||
now := time.Now()
|
||||
return fmt.Sprintf(
|
||||
"%s/%s/%s:%s-%s",
|
||||
h.CommitImageRegistry,
|
||||
devbox.Namespace,
|
||||
devbox.Name,
|
||||
rand.String(5),
|
||||
now.Format("2006-01-02-150405"),
|
||||
)
|
||||
}
|
||||
|
||||
func (h *EventHandler) removeStorage(ctx context.Context, event *corev1.Event) error {
|
||||
h.Logger.Info(
|
||||
"Starting devbox deletion Storage cleanup",
|
||||
"devbox",
|
||||
event.Name,
|
||||
"message",
|
||||
event.Message,
|
||||
)
|
||||
devboxName, contentID, baseImage := h.parseStorageCleanupAnno(event.Annotations)
|
||||
|
||||
// Use k8s.io/client-go/util/retry for robust retry logic
|
||||
err := retry.OnError(
|
||||
wait.Backoff{
|
||||
Duration: 10 * time.Second,
|
||||
Factor: 1.0,
|
||||
Jitter: 0.1,
|
||||
Steps: 30,
|
||||
},
|
||||
func(err error) bool { return true },
|
||||
func() error {
|
||||
return h.cleanupStorage(ctx, devboxName, contentID, baseImage)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
h.Logger.Error(err, "Failed to cleanup storage after all retries", "devbox", devboxName)
|
||||
return fmt.Errorf(
|
||||
"failed to cleanup storage for devbox %s after retries: %w",
|
||||
devboxName,
|
||||
err,
|
||||
)
|
||||
}
|
||||
h.Logger.Info("Successfully completed storage cleanup", "devbox", devboxName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *EventHandler) cleanupStorage(
|
||||
ctx context.Context,
|
||||
devboxName, contentID, baseImage string,
|
||||
) error {
|
||||
h.Logger.Info(
|
||||
"Starting Storage cleanup",
|
||||
"devbox",
|
||||
devboxName,
|
||||
"contentID",
|
||||
contentID,
|
||||
"baseImage",
|
||||
baseImage,
|
||||
"defaultBaseImage",
|
||||
h.DefaultBaseImage,
|
||||
)
|
||||
|
||||
// create temp container
|
||||
containerID, err := h.Committer.CreateContainer(
|
||||
ctx,
|
||||
fmt.Sprintf("temp-%s-%d", devboxName, time.Now().UnixMicro()),
|
||||
contentID,
|
||||
h.DefaultBaseImage,
|
||||
)
|
||||
if err != nil {
|
||||
h.Logger.Error(
|
||||
err,
|
||||
"failed to create temp container",
|
||||
"devbox",
|
||||
devboxName,
|
||||
"contentID",
|
||||
contentID,
|
||||
"defaultBaseImage",
|
||||
h.DefaultBaseImage,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// make sure remove container
|
||||
defer func() {
|
||||
if cleanupErr := h.Committer.RemoveContainers(
|
||||
ctx,
|
||||
[]string{containerID},
|
||||
); cleanupErr != nil {
|
||||
h.Logger.Error(
|
||||
cleanupErr,
|
||||
"failed to remove temporary container",
|
||||
"devbox",
|
||||
devboxName,
|
||||
"containerID",
|
||||
containerID,
|
||||
)
|
||||
} else {
|
||||
h.Logger.Info(
|
||||
"Successfully removed temporary container",
|
||||
"devbox",
|
||||
devboxName,
|
||||
"containerID",
|
||||
containerID,
|
||||
)
|
||||
}
|
||||
}()
|
||||
|
||||
// remove storage
|
||||
if err := h.Committer.SetLvRemovable(ctx, containerID, contentID); err != nil {
|
||||
h.Logger.Error(
|
||||
err,
|
||||
"failed to set Storage removable",
|
||||
"devbox",
|
||||
devboxName,
|
||||
"containerID",
|
||||
containerID,
|
||||
"contentID",
|
||||
contentID,
|
||||
)
|
||||
return fmt.Errorf("failed to set Storage removable: %w", err)
|
||||
}
|
||||
|
||||
h.Logger.Info(
|
||||
"Successfully completed Storage cleanup",
|
||||
"devbox",
|
||||
devboxName,
|
||||
"containerID",
|
||||
containerID,
|
||||
"contentID",
|
||||
contentID,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseStorageCleanupAnno parses the annotations from the event and returns the devboxName, contentID, and baseImage
|
||||
func (h *EventHandler) parseStorageCleanupAnno(
|
||||
annotations events.Annotations,
|
||||
) (devboxName, contentID, baseImage string) {
|
||||
devboxName = annotations[events.KeyAnnotationDevboxName]
|
||||
contentID = annotations[events.KeyAnnotationContentID]
|
||||
baseImage = annotations[events.KeyAnnotationBaseImage]
|
||||
return devboxName, contentID, baseImage
|
||||
}
|
||||
@@ -1,351 +0,0 @@
|
||||
// Copyright © 2024 sealos.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package helper
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
devboxv1alpha2 "github.com/labring/sealos/controllers/devbox/api/v1alpha2"
|
||||
utilsresource "github.com/labring/sealos/controllers/devbox/internal/controller/utils/resource"
|
||||
"github.com/labring/sealos/controllers/devbox/label"
|
||||
"golang.org/x/crypto/ssh"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
const (
|
||||
DevBoxPartOf = "devbox"
|
||||
)
|
||||
|
||||
type DevboxPodOptions func(pod *corev1.Pod)
|
||||
|
||||
func WithPodImage(image string) DevboxPodOptions {
|
||||
return func(pod *corev1.Pod) {
|
||||
pod.Spec.Containers[0].Image = image
|
||||
}
|
||||
}
|
||||
|
||||
func WithPodContentID(contentID string) DevboxPodOptions {
|
||||
return func(pod *corev1.Pod) {
|
||||
if pod.Annotations == nil {
|
||||
pod.Annotations = make(map[string]string)
|
||||
}
|
||||
pod.Annotations[devboxv1alpha2.AnnotationContentID] = contentID
|
||||
}
|
||||
}
|
||||
|
||||
func WithPodRuntimeHandler(runtime string) DevboxPodOptions {
|
||||
return func(pod *corev1.Pod) {
|
||||
if pod.Annotations == nil {
|
||||
pod.Annotations = make(map[string]string)
|
||||
}
|
||||
pod.Annotations[devboxv1alpha2.AnnotationRuntime] = runtime
|
||||
}
|
||||
}
|
||||
|
||||
func WithPodInit(init string) DevboxPodOptions {
|
||||
return func(pod *corev1.Pod) {
|
||||
if pod.Annotations == nil {
|
||||
pod.Annotations = make(map[string]string)
|
||||
}
|
||||
pod.Annotations[devboxv1alpha2.AnnotationInit] = init
|
||||
}
|
||||
}
|
||||
|
||||
func WithPodAnnotations(annotations map[string]string) DevboxPodOptions {
|
||||
return func(pod *corev1.Pod) {
|
||||
if pod.Annotations == nil {
|
||||
pod.Annotations = make(map[string]string)
|
||||
}
|
||||
for k, v := range annotations {
|
||||
pod.Annotations[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithPodLabels(labels map[string]string) DevboxPodOptions {
|
||||
return func(pod *corev1.Pod) {
|
||||
if pod.Labels == nil {
|
||||
pod.Labels = make(map[string]string)
|
||||
}
|
||||
for k, v := range labels {
|
||||
pod.Labels[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithPodNodeName(nodeName string) DevboxPodOptions {
|
||||
return func(pod *corev1.Pod) {
|
||||
pod.Spec.NodeName = nodeName
|
||||
}
|
||||
}
|
||||
|
||||
func NewContentID() string {
|
||||
return uuid.New().String()
|
||||
}
|
||||
|
||||
func GeneratePodLabels(devbox *devboxv1alpha2.Devbox) map[string]string {
|
||||
labels := make(map[string]string)
|
||||
|
||||
if devbox.Spec.Config.Labels != nil {
|
||||
for k, v := range devbox.Spec.Config.Labels {
|
||||
labels[k] = v
|
||||
}
|
||||
}
|
||||
recLabels := label.RecommendedLabels(&label.Recommended{
|
||||
Name: devbox.Name,
|
||||
ManagedBy: label.DefaultManagedBy,
|
||||
PartOf: DevBoxPartOf,
|
||||
})
|
||||
for k, v := range recLabels {
|
||||
labels[k] = v
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
func GeneratePodAnnotations(
|
||||
devbox *devboxv1alpha2.Devbox,
|
||||
enableBlockIOResource bool,
|
||||
) map[string]string {
|
||||
annotations := make(map[string]string)
|
||||
if devbox.Spec.Config.Annotations != nil {
|
||||
for k, v := range devbox.Spec.Config.Annotations {
|
||||
annotations[k] = v
|
||||
}
|
||||
}
|
||||
annotations[devboxv1alpha2.AnnotationStorageLimit] = devbox.Spec.StorageLimit
|
||||
// If BlockIOClass is enabled, add the annotation for BlockIOResources.
|
||||
// Currently we use a hardcoded value but may make it user configurable later.
|
||||
if enableBlockIOResource {
|
||||
annotations[devboxv1alpha2.AnnotationBlockIOResources] = "Devbox"
|
||||
}
|
||||
return annotations
|
||||
}
|
||||
|
||||
func GenerateSSHKeyPair() ([]byte, []byte, error) {
|
||||
pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pemKey, err := ssh.MarshalPrivateKey(privKey, "")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
privateKey := pem.EncodeToMemory(pemKey)
|
||||
publicKey, err := ssh.NewPublicKey(pubKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sshPublicKey := ssh.MarshalAuthorizedKey(publicKey)
|
||||
return sshPublicKey, privateKey, nil
|
||||
}
|
||||
|
||||
// GenerateEnvProfile generates the env profile for the Devbox pod
|
||||
// use devbox.Spec.Config.Env, generate an profile.d script
|
||||
func GenerateEnvProfile(devbox *devboxv1alpha2.Devbox, devboxJWTSecret []byte) []byte {
|
||||
envProfile := []byte("# Generated by Sealos Devbox\n")
|
||||
for _, env := range devbox.Spec.Config.Env {
|
||||
envProfile = append(
|
||||
envProfile,
|
||||
[]byte(fmt.Sprintf("export %s=\"%s\"\n", env.Name, env.Value))...)
|
||||
}
|
||||
envProfile = append(
|
||||
envProfile,
|
||||
[]byte(fmt.Sprintf("export DEVBOX_JWT_SECRET=\"%s\"\n", devboxJWTSecret))...)
|
||||
return envProfile
|
||||
}
|
||||
|
||||
func GenerateSSHVolumeMounts() []corev1.VolumeMount {
|
||||
return []corev1.VolumeMount{
|
||||
{
|
||||
Name: "devbox-ssh-keys",
|
||||
MountPath: "/usr/start/.ssh/authorized_keys",
|
||||
SubPath: "authorized_keys",
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: "devbox-ssh-keys",
|
||||
MountPath: "/usr/start/.ssh/id.pub",
|
||||
SubPath: "id.pub",
|
||||
ReadOnly: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func GenerateEnvProfileVolumeMount() []corev1.VolumeMount {
|
||||
return []corev1.VolumeMount{
|
||||
{
|
||||
Name: "devbox-env-profile",
|
||||
MountPath: "/etc/profile.d/env-profile.sh",
|
||||
SubPath: "env-profile.sh",
|
||||
ReadOnly: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateSSHVolume generates a volume for SSH keys
|
||||
func GenerateSSHVolume(devbox *devboxv1alpha2.Devbox) corev1.Volume {
|
||||
return corev1.Volume{
|
||||
Name: "devbox-ssh-keys",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: devbox.Name,
|
||||
Items: []corev1.KeyToPath{
|
||||
{
|
||||
Key: "SEALOS_DEVBOX_PUBLIC_KEY",
|
||||
Path: "id.pub",
|
||||
},
|
||||
{
|
||||
Key: "SEALOS_DEVBOX_AUTHORIZED_KEYS",
|
||||
Path: "authorized_keys",
|
||||
},
|
||||
},
|
||||
DefaultMode: ptr.To(int32(420)),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func GenerateEnvProfileVolume(devbox *devboxv1alpha2.Devbox) corev1.Volume {
|
||||
return corev1.Volume{
|
||||
Name: "devbox-env-profile",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: devbox.Name,
|
||||
Items: []corev1.KeyToPath{
|
||||
{
|
||||
Key: "SEALOS_DEVBOX_ENV_PROFILE",
|
||||
Path: "env-profile.sh",
|
||||
},
|
||||
},
|
||||
DefaultMode: ptr.To(int32(420)),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateResourceRequirements generates the resource requirements for the Devbox pod
|
||||
func GenerateResourceRequirements(
|
||||
devbox *devboxv1alpha2.Devbox,
|
||||
requestRate utilsresource.RequestRate,
|
||||
ephemeralStorage utilsresource.EphemeralStorage,
|
||||
) corev1.ResourceRequirements {
|
||||
return corev1.ResourceRequirements{
|
||||
Limits: calculateResourceLimit(devbox.Spec.Resource, ephemeralStorage),
|
||||
Requests: calculateResourceRequest(devbox.Spec.Resource, requestRate, ephemeralStorage),
|
||||
}
|
||||
}
|
||||
|
||||
func calculateResourceLimit(
|
||||
original corev1.ResourceList,
|
||||
ephemeralStorage utilsresource.EphemeralStorage,
|
||||
) corev1.ResourceList {
|
||||
limit := original.DeepCopy()
|
||||
// If ephemeral storage limit is not set, set it to default limit
|
||||
if l, ok := limit[corev1.ResourceEphemeralStorage]; !ok {
|
||||
limit[corev1.ResourceEphemeralStorage] = ephemeralStorage.DefaultLimit
|
||||
} else if l.AsApproximateFloat64() > ephemeralStorage.MaximumLimit.AsApproximateFloat64() {
|
||||
// Check if the resource limit for ephemeral storage is set and compare it, if it is exceeded the maximum limit, set it to maximum limit
|
||||
limit[corev1.ResourceEphemeralStorage] = ephemeralStorage.MaximumLimit
|
||||
}
|
||||
return limit
|
||||
}
|
||||
|
||||
func calculateResourceRequest(
|
||||
original corev1.ResourceList,
|
||||
requestRate utilsresource.RequestRate,
|
||||
ephemeralStorage utilsresource.EphemeralStorage,
|
||||
) corev1.ResourceList {
|
||||
// deep copy limit to request, only cpu and memory are calculated
|
||||
request := original.DeepCopy()
|
||||
// Calculate CPU request
|
||||
if cpu, ok := original[corev1.ResourceCPU]; ok {
|
||||
cpuValue := cpu.AsApproximateFloat64()
|
||||
cpuRequest := cpuValue / requestRate.CPU
|
||||
request[corev1.ResourceCPU] = *resource.NewMilliQuantity(int64(cpuRequest*1000), resource.DecimalSI)
|
||||
}
|
||||
// Calculate memory request
|
||||
if memory, ok := original[corev1.ResourceMemory]; ok {
|
||||
memoryValue := memory.AsApproximateFloat64()
|
||||
memoryRequest := memoryValue / requestRate.Memory
|
||||
request[corev1.ResourceMemory] = *resource.NewQuantity(int64(memoryRequest), resource.BinarySI)
|
||||
}
|
||||
// Set ephemeral storage request to default request
|
||||
request[corev1.ResourceEphemeralStorage] = ephemeralStorage.DefaultRequest
|
||||
return request
|
||||
}
|
||||
|
||||
// GetWorkingDir get the working directory for the Devbox pod
|
||||
func GetWorkingDir(devbox *devboxv1alpha2.Devbox) string {
|
||||
return devbox.Spec.Config.WorkingDir
|
||||
}
|
||||
|
||||
// GetCommand get the command for the Devbox pod
|
||||
func GetCommand(devbox *devboxv1alpha2.Devbox) []string {
|
||||
return devbox.Spec.Config.Command
|
||||
}
|
||||
|
||||
// GetArgs get the arguments for the Devbox pod
|
||||
func GetArgs(devbox *devboxv1alpha2.Devbox) []string {
|
||||
return devbox.Spec.Config.Args
|
||||
}
|
||||
|
||||
func IsExceededQuotaError(err error) bool {
|
||||
return strings.Contains(err.Error(), "exceeded quota")
|
||||
}
|
||||
|
||||
func GetStorageLimitInBytes(devbox *devboxv1alpha2.Devbox) (int64, error) {
|
||||
if devbox.Spec.StorageLimit != "" {
|
||||
storageLimit, err := resource.ParseQuantity(devbox.Spec.StorageLimit)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return storageLimit.Value(), nil
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// GenerateStartupVolume generates a volume for the startup script configmap
|
||||
func GenerateStartupVolume(devbox *devboxv1alpha2.Devbox) corev1.Volume {
|
||||
return corev1.Volume{
|
||||
Name: "devbox-startup",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: devbox.Name,
|
||||
},
|
||||
DefaultMode: ptr.To(int32(0o755)),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateStartupVolumeMounts generates volume mounts for the startup script
|
||||
func GenerateStartupVolumeMounts() []corev1.VolumeMount {
|
||||
return []corev1.VolumeMount{
|
||||
{
|
||||
Name: "devbox-startup",
|
||||
MountPath: "/usr/start/startup.sh",
|
||||
SubPath: "startup.sh",
|
||||
ReadOnly: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package helper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
devboxv1alpha2 "github.com/labring/sealos/controllers/devbox/api/v1alpha2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func TestGenerateEnvProfile(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
envs []corev1.EnvVar
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "no env variables",
|
||||
envs: nil,
|
||||
want: "# Generated by Sealos Devbox\n",
|
||||
},
|
||||
{
|
||||
name: "multiple env variables",
|
||||
envs: []corev1.EnvVar{
|
||||
{Name: "FOO", Value: "bar"},
|
||||
{Name: "HELLO", Value: "world"},
|
||||
},
|
||||
want: "# Generated by Sealos Devbox\n" +
|
||||
"export FOO=\"bar\"\n" +
|
||||
"export HELLO=\"world\"\n" +
|
||||
"export SEALOS_DEVBOX_JWT_SECRET=\"test-jwt-secret\"\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
devbox := &devboxv1alpha2.Devbox{
|
||||
Spec: devboxv1alpha2.DevboxSpec{
|
||||
Config: devboxv1alpha2.Config{
|
||||
Env: tt.envs,
|
||||
},
|
||||
},
|
||||
}
|
||||
got := string(GenerateEnvProfile(devbox, []byte("test-jwt-secret")))
|
||||
fmt.Print(got)
|
||||
if got != tt.want {
|
||||
t.Fatalf("GenerateEnvProfile() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
// Copyright © 2024 sealos.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package helper
|
||||
|
||||
import (
|
||||
devboxv1alpha2 "github.com/labring/sealos/controllers/devbox/api/v1alpha2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// State to Phase Mapping Table:
|
||||
//
|
||||
// DevboxState (Spec.State) -> DevboxPhase (Status.Phase)
|
||||
//
|
||||
// Running State:
|
||||
// - Has running pod -> Running
|
||||
// - Has pending pod / No pod -> Pending
|
||||
// - Failed pod -> Error
|
||||
// Paused State:
|
||||
// - Has running pod -> Pausing
|
||||
// - Has pending pod / No pod -> Paused
|
||||
// Stopped/Shutdown States:
|
||||
// - CommitRecord.node != "" -> Transitioning phase (Stopping/Shutting)
|
||||
// - CommitRecord.node == "" -> Final phase (Stopped/Shutdown)
|
||||
|
||||
// GetLatestCommitRecord returns the latest commit record for the given contentID
|
||||
func GetLatestCommitRecord(
|
||||
commitRecords devboxv1alpha2.CommitRecordMap,
|
||||
contentID string,
|
||||
) *devboxv1alpha2.CommitRecord {
|
||||
if len(commitRecords) == 0 || contentID == "" {
|
||||
return nil
|
||||
}
|
||||
return commitRecords[contentID]
|
||||
}
|
||||
|
||||
// PodStatus represents the aggregated status of pods
|
||||
type PodStatus struct {
|
||||
hasRunning bool
|
||||
hasPending bool
|
||||
hasFailed bool
|
||||
hasTerminating bool
|
||||
podCount int
|
||||
}
|
||||
|
||||
// AnalyzePodStatus analyzes the pod list and returns aggregated pod status
|
||||
func AnalyzePodStatus(podList *corev1.PodList) PodStatus {
|
||||
status := PodStatus{
|
||||
podCount: len(podList.Items),
|
||||
}
|
||||
for i := range podList.Items {
|
||||
p := &podList.Items[i]
|
||||
if !p.DeletionTimestamp.IsZero() {
|
||||
status.hasTerminating = true
|
||||
}
|
||||
switch p.Status.Phase {
|
||||
case corev1.PodRunning:
|
||||
status.hasRunning = true
|
||||
case corev1.PodPending:
|
||||
status.hasPending = true
|
||||
case corev1.PodFailed:
|
||||
status.hasFailed = true
|
||||
}
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
// DerivePhase derives phase based on desired state, pod status, and commit record.
|
||||
// It implements the State to Phase Mapping Table defined above.
|
||||
func DerivePhase(
|
||||
desiredState devboxv1alpha2.DevboxState,
|
||||
podStatus PodStatus,
|
||||
commitRecord *devboxv1alpha2.CommitRecord,
|
||||
) devboxv1alpha2.DevboxPhase {
|
||||
// Failed pod -> Error (applies to all states)
|
||||
if podStatus.hasFailed {
|
||||
return devboxv1alpha2.DevboxPhaseError
|
||||
}
|
||||
|
||||
switch desiredState {
|
||||
case devboxv1alpha2.DevboxStateRunning:
|
||||
// Running State:
|
||||
// - Has running pod -> Running
|
||||
// - Has pending pod / No pod -> Pending
|
||||
if podStatus.hasRunning {
|
||||
return devboxv1alpha2.DevboxPhaseRunning
|
||||
}
|
||||
return devboxv1alpha2.DevboxPhasePending
|
||||
|
||||
case devboxv1alpha2.DevboxStatePaused:
|
||||
// Paused State:
|
||||
// - Has running pod -> Pausing
|
||||
// - Has pending pod / No pod -> Paused
|
||||
if podStatus.hasRunning {
|
||||
return devboxv1alpha2.DevboxPhasePausing
|
||||
}
|
||||
return devboxv1alpha2.DevboxPhasePaused
|
||||
|
||||
case devboxv1alpha2.DevboxStateStopped:
|
||||
// Stopped State:
|
||||
// - CommitRecord.node != "" -> Stopping
|
||||
// - CommitRecord.node == "" -> Stopped
|
||||
if commitRecord != nil && commitRecord.Node != "" {
|
||||
return devboxv1alpha2.DevboxPhaseStopping
|
||||
}
|
||||
return devboxv1alpha2.DevboxPhaseStopped
|
||||
|
||||
case devboxv1alpha2.DevboxStateShutdown:
|
||||
// Shutdown State:
|
||||
// - CommitRecord.node != "" -> Shutting
|
||||
// - CommitRecord.node == "" -> Shutdown
|
||||
if commitRecord != nil && commitRecord.Node != "" {
|
||||
return devboxv1alpha2.DevboxPhaseShutting
|
||||
}
|
||||
return devboxv1alpha2.DevboxPhaseShutdown
|
||||
|
||||
default:
|
||||
return devboxv1alpha2.DevboxPhaseUnknown
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package helper
|
||||
|
||||
const (
|
||||
DefaultContainerFSAvailableThreshold = 10.0
|
||||
DefaultCPURequestRatio = 1.0
|
||||
DefaultCPULimitRatio = 2.0
|
||||
DefaultMemoryRequestRatio = 1.0
|
||||
DefaultMemoryLimitRatio = 2.0
|
||||
)
|
||||
|
||||
type AcceptanceConsideration struct {
|
||||
// The percentage of available bytes required to consider the node suitable for scheduling devbox.
|
||||
// Default is 10.0, which means the node must have at least 10% of available bytes in the container filesystem.
|
||||
ContainerFSAvailableThreshold float64
|
||||
// The ratio of expected overcommitment (total cpu request / available cpu) of CPU request.
|
||||
// Default is 1.0, which means the CPU request cannot be overcommited by more than 100%.
|
||||
CPURequestRatio float64
|
||||
// The ratio of expected overcommitment (total cpu limit / available cpu) of CPU limit.
|
||||
// Default is 2.0, which means the CPU limit cannot be overcommited by more than 200%.
|
||||
CPULimitRatio float64
|
||||
// The ratio of expected overcommitment (total memory request / available memory) of Memory request.
|
||||
// Default is 1.0, which means the Memory request cannot be overcommited by more than 100%.
|
||||
MemoryRequestRatio float64
|
||||
// The ratio of expected overcommitment (total memory limit / available memory) of Memory limit.
|
||||
// Default is 2.0, which means the Memory limit cannot be overcommited by more than 200%.
|
||||
MemoryLimitRatio float64
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
devboxv1alpha2 "github.com/labring/sealos/controllers/devbox/api/v1alpha2"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
)
|
||||
|
||||
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
|
||||
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
|
||||
|
||||
var (
|
||||
cfg *rest.Config
|
||||
k8sClient client.Client
|
||||
testEnv *envtest.Environment
|
||||
)
|
||||
|
||||
func TestControllers(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
|
||||
RunSpecs(t, "Controller Suite")
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func() {
|
||||
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
|
||||
|
||||
By("bootstrapping test environment")
|
||||
testEnv = &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")},
|
||||
ErrorIfCRDPathMissing: true,
|
||||
|
||||
// The BinaryAssetsDirectory is only required if you want to run the tests directly
|
||||
// without call the makefile target test. If not informed it will look for the
|
||||
// default path defined in controller-runtime which is /usr/local/kubebuilder/.
|
||||
// Note that you must have the required binaries setup under the bin directory to perform
|
||||
// the tests directly. When we run make test it will be setup and used automatically.
|
||||
BinaryAssetsDirectory: filepath.Join("..", "..", "bin", "k8s",
|
||||
fmt.Sprintf("1.30.0-%s-%s", runtime.GOOS, runtime.GOARCH)),
|
||||
}
|
||||
|
||||
var err error
|
||||
// cfg is defined in this file globally.
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cfg).NotTo(BeNil())
|
||||
|
||||
err = devboxv1alpha2.AddToScheme(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// +kubebuilder:scaffold:scheme
|
||||
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(k8sClient).NotTo(BeNil())
|
||||
})
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("tearing down the test environment")
|
||||
err := testEnv.Stop()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright © 2024 sealos.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package errors
|
||||
|
||||
import "errors"
|
||||
|
||||
var ErrPodNotLastCommitHistory = errors.New("pod is not the last commit history pod")
|
||||
|
||||
// ErrDevboxNotFound is returned when devbox is not found during commit
|
||||
var ErrDevboxNotFound = errors.New("devbox not found")
|
||||
@@ -1,22 +0,0 @@
|
||||
package events
|
||||
|
||||
const (
|
||||
ReasonStorageCleanupRequested = "storage-cleanup-requested"
|
||||
ReasonDevboxStateChanged = "devbox-state-changed"
|
||||
|
||||
KeyAnnotationReason = "reason"
|
||||
KeyAnnotationDevboxName = "devbox-name"
|
||||
KeyAnnotationContentID = "content-id"
|
||||
KeyAnnotationBaseImage = "base-image"
|
||||
)
|
||||
|
||||
type Annotations map[string]string
|
||||
|
||||
func BuildStorageCleanupAnnotations(devboxName, contentID, baseImage string) Annotations {
|
||||
return Annotations{
|
||||
KeyAnnotationReason: ReasonStorageCleanupRequested,
|
||||
KeyAnnotationDevboxName: devboxName,
|
||||
KeyAnnotationContentID: contentID,
|
||||
KeyAnnotationBaseImage: baseImage,
|
||||
}
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
// Copyright © 2024 sealos.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package matcher
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
devboxv1alpha2 "github.com/labring/sealos/controllers/devbox/api/v1alpha2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
type PodMatcher interface {
|
||||
Match(expectPod, pod *corev1.Pod) bool
|
||||
}
|
||||
|
||||
type ResourceMatcher struct{}
|
||||
|
||||
func (r ResourceMatcher) Match(expectPod, pod *corev1.Pod) bool {
|
||||
if len(pod.Spec.Containers) == 0 {
|
||||
slog.Info("Pod has no containers")
|
||||
return false
|
||||
}
|
||||
container := pod.Spec.Containers[0]
|
||||
expectContainer := expectPod.Spec.Containers[0]
|
||||
|
||||
if container.Resources.Requests.Cpu().Cmp(*expectContainer.Resources.Requests.Cpu()) != 0 {
|
||||
slog.Info("CPU requests are not equal")
|
||||
return false
|
||||
}
|
||||
if container.Resources.Limits.Cpu().Cmp(*expectContainer.Resources.Limits.Cpu()) != 0 {
|
||||
slog.Info("CPU limits are not equal")
|
||||
return false
|
||||
}
|
||||
if container.Resources.Requests.Memory().
|
||||
Cmp(*expectContainer.Resources.Requests.Memory()) !=
|
||||
0 {
|
||||
slog.Info("Memory requests are not equal")
|
||||
return false
|
||||
}
|
||||
if container.Resources.Limits.Memory().Cmp(*expectContainer.Resources.Limits.Memory()) != 0 {
|
||||
slog.Info("Memory limits are not equal")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type EphemeralStorageMatcher struct{}
|
||||
|
||||
func (e EphemeralStorageMatcher) Match(expectPod, pod *corev1.Pod) bool {
|
||||
if len(pod.Spec.Containers) == 0 {
|
||||
slog.Info("Pod has no containers")
|
||||
return false
|
||||
}
|
||||
container := pod.Spec.Containers[0]
|
||||
expectContainer := expectPod.Spec.Containers[0]
|
||||
|
||||
if container.Resources.Limits.StorageEphemeral().
|
||||
Cmp(*expectContainer.Resources.Limits.StorageEphemeral()) !=
|
||||
0 {
|
||||
slog.Info("Ephemeral-Storage limits are not equal")
|
||||
return false
|
||||
}
|
||||
if container.Resources.Requests.StorageEphemeral().
|
||||
Cmp(*expectContainer.Resources.Requests.StorageEphemeral()) !=
|
||||
0 {
|
||||
slog.Info("Ephemeral-Storage requests are not equal")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type EnvVarMatcher struct{}
|
||||
|
||||
func (e EnvVarMatcher) Match(expectPod, pod *corev1.Pod) bool {
|
||||
if len(pod.Spec.Containers) == 0 {
|
||||
slog.Info("Pod has no containers")
|
||||
return false
|
||||
}
|
||||
container := pod.Spec.Containers[0]
|
||||
expectContainer := expectPod.Spec.Containers[0]
|
||||
|
||||
if len(container.Env) != len(expectContainer.Env) {
|
||||
slog.Info("Environment variable count mismatch")
|
||||
return false
|
||||
}
|
||||
|
||||
for _, env := range container.Env {
|
||||
found := false
|
||||
for _, expectEnv := range expectContainer.Env {
|
||||
if env.Name == "SEALOS_COMMIT_IMAGE_NAME" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
if env.Name == expectEnv.Name && env.Value == expectEnv.Value {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
slog.Info(
|
||||
"Environment variables are not equal",
|
||||
"env not found",
|
||||
env.Name,
|
||||
"env value",
|
||||
env.Value,
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type PortMatcher struct{}
|
||||
|
||||
func (p PortMatcher) Match(expectPod, pod *corev1.Pod) bool {
|
||||
if len(pod.Spec.Containers) == 0 {
|
||||
slog.Info("Pod has no containers")
|
||||
return false
|
||||
}
|
||||
container := pod.Spec.Containers[0]
|
||||
expectContainer := expectPod.Spec.Containers[0]
|
||||
|
||||
if len(container.Ports) != len(expectContainer.Ports) {
|
||||
slog.Info("Port count mismatch")
|
||||
return false
|
||||
}
|
||||
|
||||
for _, expectPort := range expectContainer.Ports {
|
||||
found := false
|
||||
for _, podPort := range container.Ports {
|
||||
if expectPort.ContainerPort == podPort.ContainerPort &&
|
||||
expectPort.Protocol == podPort.Protocol {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
slog.Info("Ports are not equal")
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type StorageLimitMatcher struct{}
|
||||
|
||||
func (s StorageLimitMatcher) Match(expectPod, pod *corev1.Pod) bool {
|
||||
return expectPod.Annotations[devboxv1alpha2.AnnotationStorageLimit] == pod.Annotations[devboxv1alpha2.AnnotationStorageLimit]
|
||||
}
|
||||
|
||||
// PredicateCommitStatus returns the commit status of the pod
|
||||
// if the pod container id is empty, it means the pod is pending or has't started, we can assume the image has not been committed
|
||||
// otherwise, it means the pod has been started, we can assume the image has been committed
|
||||
|
||||
func PodMatchExpectations(expectPod, pod *corev1.Pod, matchers ...PodMatcher) bool {
|
||||
for _, matcher := range matchers {
|
||||
if !matcher.Match(expectPod, pod) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
// Copyright © 2024 sealos.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package matcher
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
func TestPodMatchExpectations(t *testing.T) {
|
||||
expectPod := &corev1.Pod{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Limits: corev1.ResourceList{
|
||||
corev1.ResourceCPU: resource.MustParse("500m"),
|
||||
corev1.ResourceMemory: resource.MustParse("128Mi"),
|
||||
},
|
||||
},
|
||||
Env: []corev1.EnvVar{
|
||||
{Name: "ENV_VAR_1", Value: "value1"},
|
||||
{Name: "ENV_VAR_2", Value: "value2"},
|
||||
},
|
||||
Ports: []corev1.ContainerPort{
|
||||
{ContainerPort: 8080, Protocol: corev1.ProtocolTCP},
|
||||
{ContainerPort: 9090, Protocol: corev1.ProtocolTCP},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
pod *corev1.Pod
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "consistent pod",
|
||||
pod: &corev1.Pod{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Limits: corev1.ResourceList{
|
||||
corev1.ResourceCPU: resource.MustParse("500m"),
|
||||
corev1.ResourceMemory: resource.MustParse("128Mi"),
|
||||
},
|
||||
},
|
||||
Env: []corev1.EnvVar{
|
||||
{Name: "ENV_VAR_1", Value: "value1"},
|
||||
{Name: "ENV_VAR_2", Value: "value2"},
|
||||
},
|
||||
Ports: []corev1.ContainerPort{
|
||||
{ContainerPort: 8080, Protocol: corev1.ProtocolTCP},
|
||||
{ContainerPort: 9090, Protocol: corev1.ProtocolTCP},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "inconsistent CPU",
|
||||
pod: &corev1.Pod{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Limits: corev1.ResourceList{
|
||||
corev1.ResourceCPU: resource.MustParse("1000m"),
|
||||
corev1.ResourceMemory: resource.MustParse("128Mi"),
|
||||
},
|
||||
},
|
||||
Env: []corev1.EnvVar{
|
||||
{Name: "ENV_VAR_1", Value: "value1"},
|
||||
{Name: "ENV_VAR_2", Value: "value2"},
|
||||
},
|
||||
Ports: []corev1.ContainerPort{
|
||||
{ContainerPort: 8080, Protocol: corev1.ProtocolTCP},
|
||||
{ContainerPort: 9090, Protocol: corev1.ProtocolTCP},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "inconsistent environment variable",
|
||||
pod: &corev1.Pod{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Limits: corev1.ResourceList{
|
||||
corev1.ResourceCPU: resource.MustParse("500m"),
|
||||
corev1.ResourceMemory: resource.MustParse("128Mi"),
|
||||
},
|
||||
},
|
||||
Env: []corev1.EnvVar{
|
||||
{Name: "ENV_VAR_1", Value: "value1"},
|
||||
{Name: "ENV_VAR_3", Value: "value3"},
|
||||
},
|
||||
Ports: []corev1.ContainerPort{
|
||||
{ContainerPort: 8080, Protocol: corev1.ProtocolTCP},
|
||||
{ContainerPort: 9090, Protocol: corev1.ProtocolTCP},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "inconsistent port",
|
||||
pod: &corev1.Pod{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Limits: corev1.ResourceList{
|
||||
corev1.ResourceCPU: resource.MustParse("500m"),
|
||||
corev1.ResourceMemory: resource.MustParse("128Mi"),
|
||||
},
|
||||
},
|
||||
Env: []corev1.EnvVar{
|
||||
{Name: "ENV_VAR_1", Value: "value1"},
|
||||
{Name: "ENV_VAR_2", Value: "value2"},
|
||||
},
|
||||
Ports: []corev1.ContainerPort{
|
||||
{ContainerPort: 8080, Protocol: corev1.ProtocolTCP},
|
||||
{ContainerPort: 9091, Protocol: corev1.ProtocolTCP},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
matchers := []PodMatcher{
|
||||
ResourceMatcher{},
|
||||
EnvVarMatcher{},
|
||||
PortMatcher{},
|
||||
EphemeralStorageMatcher{},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := PodMatchExpectations(expectPod, tt.pod, matchers...)
|
||||
if result != tt.expected {
|
||||
t.Errorf("CheckPodConsistency() = %v, expected %v", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func GetNodeName() string {
|
||||
nodeName := os.Getenv("NODE_NAME")
|
||||
if nodeName == "" {
|
||||
// panic if node name is not set
|
||||
panic("NODE_NAME is not set")
|
||||
}
|
||||
return nodeName
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
// Copyright © 2024 sealos.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package registry
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
)
|
||||
|
||||
// todo: refactor this struct, add opts for tls or something else
|
||||
type Opts struct{}
|
||||
|
||||
type BasicAuth struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
type Registry struct {
|
||||
Host string
|
||||
BasicAuth BasicAuth
|
||||
}
|
||||
|
||||
var ErrManifestNotFound = errors.New("manifest not found")
|
||||
|
||||
// ReTag creates a new tag for an existing image by copying its manifest.
|
||||
func (c *Registry) ReTag(source, target string) error {
|
||||
manifest, err := c.pullManifest(source)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to pull manifest for %s: %w", source, err)
|
||||
}
|
||||
if err := c.pushManifest(target, manifest); err != nil {
|
||||
return fmt.Errorf("failed to push manifest for %s: %w", target, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// todo: refactor this function, add opts for tls
|
||||
func (c *Registry) pullManifest(image string) ([]byte, error) {
|
||||
ref, err := name.ParseReference(image)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse image: %w", err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf(
|
||||
"http://%s/v2/%s/manifests/%s",
|
||||
ref.Context().RegistryStr(),
|
||||
ref.Context().RepositoryStr(),
|
||||
ref.Identifier(),
|
||||
)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create GET request: %w", err)
|
||||
}
|
||||
|
||||
req.SetBasicAuth(c.BasicAuth.Username, c.BasicAuth.Password)
|
||||
req.Header.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusNotFound:
|
||||
return nil, ErrManifestNotFound
|
||||
case http.StatusOK:
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
return body, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected status code %d: %s", resp.StatusCode, resp.Status)
|
||||
}
|
||||
}
|
||||
|
||||
// todo: refactor this function, add opts for tls
|
||||
func (c *Registry) pushManifest(image string, manifest []byte) error {
|
||||
ref, err := name.ParseReference(image)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse image: %w", err)
|
||||
}
|
||||
url := fmt.Sprintf(
|
||||
"http://%s/v2/%s/manifests/%s",
|
||||
ref.Context().RegistryStr(),
|
||||
ref.Context().RepositoryStr(),
|
||||
ref.Identifier(),
|
||||
)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPut, url, bytes.NewBuffer(manifest))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create PUT request: %w", err)
|
||||
}
|
||||
|
||||
req.SetBasicAuth(c.BasicAuth.Username, c.BasicAuth.Password)
|
||||
req.Header.Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return fmt.Errorf("unexpected status code %d: %s", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
// Copyright © 2024 sealos.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package registry
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestClient_TagImage(t1 *testing.T) {
|
||||
type fields struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
type args struct {
|
||||
source string
|
||||
target string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Test1",
|
||||
fields: fields{
|
||||
Username: "admin",
|
||||
Password: "passw0rd",
|
||||
},
|
||||
args: args{
|
||||
source: "sealos.hub:5000/default/devbox-sample:2024-08-21-072021",
|
||||
target: "sealos.hub:5000/default/devbox-sample:test",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t1.Run(tt.name, func(t1 *testing.T) {
|
||||
t := &Registry{
|
||||
BasicAuth: BasicAuth{
|
||||
Username: tt.fields.Username,
|
||||
Password: tt.fields.Password,
|
||||
},
|
||||
}
|
||||
if err := t.ReTag(tt.args.source, tt.args.target); (err != nil) != tt.wantErr {
|
||||
t1.Errorf("TagImage() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// Copyright © 2024 sealos.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package resource
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
type RequestRate struct {
|
||||
CPU float64
|
||||
Memory float64
|
||||
}
|
||||
|
||||
type EphemeralStorage struct {
|
||||
DefaultRequest resource.Quantity
|
||||
DefaultLimit resource.Quantity
|
||||
MaximumLimit resource.Quantity
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package rwords
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var rng = struct {
|
||||
sync.Mutex
|
||||
rand *rand.Rand
|
||||
}{
|
||||
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
}
|
||||
|
||||
// Int returns a non-negative pseudo-random int.
|
||||
func Int() int {
|
||||
rng.Lock()
|
||||
defer rng.Unlock()
|
||||
return rng.rand.Int()
|
||||
}
|
||||
|
||||
// Intn generates an integer in range [0,max).
|
||||
// By design this should panic if input is invalid, <= 0.
|
||||
func Intn(maxVal int) int {
|
||||
rng.Lock()
|
||||
defer rng.Unlock()
|
||||
return rng.rand.Intn(maxVal)
|
||||
}
|
||||
|
||||
// IntnRange generates an integer in range [min,max).
|
||||
// By design this should panic if input is invalid, <= 0.
|
||||
func IntnRange(minVal, maxVal int) int {
|
||||
rng.Lock()
|
||||
defer rng.Unlock()
|
||||
return rng.rand.Intn(maxVal-minVal) + minVal
|
||||
}
|
||||
|
||||
// Int63nRange generates an int64 integer in range [min,max).
|
||||
// By design this should panic if input is invalid, <= 0.
|
||||
func Int63nRange(minVal, maxVal int64) int64 {
|
||||
rng.Lock()
|
||||
defer rng.Unlock()
|
||||
return rng.rand.Int63n(maxVal-minVal) + minVal
|
||||
}
|
||||
|
||||
// Seed seeds the rng with the provided seed.
|
||||
func Seed(seed int64) {
|
||||
rng.Lock()
|
||||
defer rng.Unlock()
|
||||
|
||||
rng.rand = rand.New(rand.NewSource(seed))
|
||||
}
|
||||
|
||||
// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers [0,n)
|
||||
// from the default Source.
|
||||
func Perm(n int) []int {
|
||||
rng.Lock()
|
||||
defer rng.Unlock()
|
||||
return rng.rand.Perm(n)
|
||||
}
|
||||
|
||||
const (
|
||||
// make a `good random string`
|
||||
alphanums = "abcdefhiklmnorstuvwxz"
|
||||
// No. of bits required to index into alphanums string.
|
||||
alphanumsIdxBits = 5
|
||||
// Mask used to extract last alphanumsIdxBits of an int.
|
||||
alphanumsIdxMask = 1<<alphanumsIdxBits - 1
|
||||
// No. of random letters we can extract from a single int63.
|
||||
maxAlphanumsPerInt = 63 / alphanumsIdxBits
|
||||
)
|
||||
|
||||
// String generates a random consonant-only string which is n
|
||||
// characters long. This will panic if n is less than zero.
|
||||
// How the random string is created:
|
||||
// - we generate random int63's
|
||||
// - from each int63, we are extracting multiple random letters by bit-shifting and masking
|
||||
// - if some index is out of range of alphanums we neglect it (unlikely to happen multiple times in a row)
|
||||
func String(n int) string {
|
||||
b := make([]byte, n)
|
||||
rng.Lock()
|
||||
defer rng.Unlock()
|
||||
|
||||
randomInt63 := rng.rand.Int63()
|
||||
remaining := maxAlphanumsPerInt
|
||||
for i := 0; i < n; {
|
||||
if remaining == 0 {
|
||||
randomInt63, remaining = rng.rand.Int63(), maxAlphanumsPerInt
|
||||
}
|
||||
if idx := int(randomInt63 & alphanumsIdxMask); idx < len(alphanums) {
|
||||
b[i] = alphanums[idx]
|
||||
i++
|
||||
}
|
||||
randomInt63 >>= alphanumsIdxBits
|
||||
remaining--
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// SafeEncodeString encodes s using the same characters as rand.String. This reduces the chances of bad words and
|
||||
// ensures that strings generated from hash functions appear consistent throughout the API.
|
||||
func SafeEncodeString(s string) string {
|
||||
r := make([]byte, len(s))
|
||||
for i, b := range []rune(s) {
|
||||
r[i] = alphanums[(int(b) % len(alphanums))]
|
||||
}
|
||||
return string(r)
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package rwords
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const listLength = len(wordsList)
|
||||
|
||||
// data size is 2048*2048*21^4=815,712,436,224
|
||||
// compare to 8 random characters, data size is 36^8 = 208,827,064,576
|
||||
// outputs looks like: "abandon-ability-abcd"
|
||||
func GenerateRandomWords() string {
|
||||
return fmt.Sprintf(
|
||||
"%s-%s-%s",
|
||||
wordsList[Intn(listLength)],
|
||||
wordsList[Intn(listLength)],
|
||||
String(4),
|
||||
)
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
package rwords
|
||||
|
||||
// length of wordsList is 2048
|
||||
var wordsList = [...]string{
|
||||
"abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract", "absurd", "abuse",
|
||||
"access", "accident", "account", "accuse", "achieve", "acid", "acoustic", "acquire", "across", "act",
|
||||
"action", "actor", "actress", "actual", "adapt", "add", "addict", "address", "adjust", "admit",
|
||||
"adult", "advance", "advice", "aerobic", "affair", "afford", "afraid", "again", "age", "agent",
|
||||
"agree", "ahead", "aim", "air", "airport", "aisle", "alarm", "album", "alcohol", "alert",
|
||||
"alien", "all", "alley", "allow", "almost", "alone", "alpha", "already", "also", "alter",
|
||||
"always", "amateur", "amazing", "among", "amount", "amused", "analyst", "anchor", "ancient", "anger",
|
||||
"angle", "angry", "animal", "ankle", "announce", "annual", "another", "answer", "antenna", "antique",
|
||||
"anxiety", "any", "apart", "apology", "appear", "apple", "approve", "april", "arch", "arctic",
|
||||
"area", "arena", "argue", "arm", "armed", "armor", "army", "around", "arrange", "arrest",
|
||||
"arrive", "arrow", "art", "artefact", "artist", "artwork", "ask", "aspect", "assault", "asset",
|
||||
"assist", "assume", "asthma", "athlete", "atom", "attack", "attend", "attitude", "attract", "auction",
|
||||
"audit", "august", "aunt", "author", "auto", "autumn", "average", "avocado", "avoid", "awake",
|
||||
"aware", "away", "awesome", "awful", "awkward", "axis", "baby", "bachelor", "bacon", "badge",
|
||||
"bag", "balance", "balcony", "ball", "bamboo", "banana", "banner", "bar", "barely", "bargain",
|
||||
"barrel", "base", "basic", "basket", "battle", "beach", "bean", "beauty", "because", "become",
|
||||
"beef", "before", "begin", "behave", "behind", "believe", "below", "belt", "bench", "benefit",
|
||||
"best", "betray", "better", "between", "beyond", "bicycle", "bid", "bike", "bind", "biology",
|
||||
"bird", "birth", "bitter", "black", "blade", "blame", "blanket", "blast", "bleak", "bless",
|
||||
"blind", "blood", "blossom", "blouse", "blue", "blur", "blush", "board", "boat", "body",
|
||||
"boil", "bomb", "bone", "bonus", "book", "boost", "border", "boring", "borrow", "boss",
|
||||
"bottom", "bounce", "box", "boy", "bracket", "brain", "brand", "brass", "brave", "bread",
|
||||
"breeze", "brick", "bridge", "brief", "bright", "bring", "brisk", "broccoli", "broken", "bronze",
|
||||
"broom", "brother", "brown", "brush", "bubble", "buddy", "budget", "buffalo", "build", "bulb",
|
||||
"bulk", "bullet", "bundle", "bunker", "burden", "burger", "burst", "bus", "business", "busy",
|
||||
"butter", "buyer", "buzz", "cabbage", "cabin", "cable", "cactus", "cage", "cake", "call",
|
||||
"calm", "camera", "camp", "can", "canal", "cancel", "candy", "cannon", "canoe", "canvas",
|
||||
"canyon", "capable", "capital", "captain", "car", "carbon", "card", "cargo", "carpet", "carry",
|
||||
"cart", "case", "cash", "casino", "castle", "casual", "cat", "catalog", "catch", "category",
|
||||
"cattle", "caught", "cause", "caution", "cave", "ceiling", "celery", "cement", "census", "century",
|
||||
"cereal", "certain", "chair", "chalk", "champion", "change", "chaos", "chapter", "charge", "chase",
|
||||
"chat", "cheap", "check", "cheese", "chef", "cherry", "chest", "chicken", "chief", "child",
|
||||
"chimney", "choice", "choose", "chronic", "chuckle", "chunk", "churn", "cigar", "cinnamon", "circle",
|
||||
"citizen", "city", "civil", "claim", "clap", "clarify", "claw", "clay", "clean", "clerk",
|
||||
"clever", "click", "client", "cliff", "climb", "clinic", "clip", "clock", "clog", "close",
|
||||
"cloth", "cloud", "clown", "club", "clump", "cluster", "clutch", "coach", "coast", "coconut",
|
||||
"code", "coffee", "coil", "coin", "collect", "color", "column", "combine", "come", "comfort",
|
||||
"comic", "common", "company", "concert", "conduct", "confirm", "congress", "connect", "consider", "control",
|
||||
"convince", "cook", "cool", "copper", "copy", "coral", "core", "corn", "correct", "cost",
|
||||
"cotton", "couch", "country", "couple", "course", "cousin", "cover", "coyote", "crack", "cradle",
|
||||
"craft", "cram", "crane", "crash", "crater", "crawl", "crazy", "cream", "credit", "creek",
|
||||
"crew", "cricket", "crime", "crisp", "critic", "crop", "cross", "crouch", "crowd", "crucial",
|
||||
"cruel", "cruise", "crumble", "crunch", "crush", "cry", "crystal", "cube", "culture", "cup",
|
||||
"cupboard", "curious", "current", "curtain", "curve", "cushion", "custom", "cute", "cycle", "dad",
|
||||
"damage", "damp", "dance", "danger", "daring", "dash", "daughter", "dawn", "day", "deal",
|
||||
"debate", "debris", "decade", "december", "decide", "decline", "decorate", "decrease", "deer", "defense",
|
||||
"define", "defy", "degree", "delay", "deliver", "demand", "demise", "denial", "dentist", "deny",
|
||||
"depart", "depend", "deposit", "depth", "deputy", "derive", "describe", "desert", "design", "desk",
|
||||
"despair", "destroy", "detail", "detect", "develop", "device", "devote", "diagram", "dial", "diamond",
|
||||
"diary", "dice", "diesel", "diet", "differ", "digital", "dignity", "dilemma", "dinner", "dinosaur",
|
||||
"direct", "dirt", "disagree", "discover", "disease", "dish", "dismiss", "disorder", "display", "distance",
|
||||
"divert", "divide", "divorce", "dizzy", "doctor", "document", "dog", "doll", "dolphin", "domain",
|
||||
"donate", "donkey", "donor", "door", "dose", "double", "dove", "draft", "dragon", "drama",
|
||||
"drastic", "draw", "dream", "dress", "drift", "drill", "drink", "drip", "drive", "drop",
|
||||
"drum", "dry", "duck", "dumb", "dune", "during", "dust", "dutch", "duty", "dwarf",
|
||||
"dynamic", "eager", "eagle", "early", "earn", "earth", "easily", "east", "easy", "echo",
|
||||
"ecology", "economy", "edge", "edit", "educate", "effort", "egg", "eight", "either", "elbow",
|
||||
"elder", "electric", "elegant", "element", "elephant", "elevator", "elite", "else", "embark", "embody",
|
||||
"embrace", "emerge", "emotion", "employ", "empower", "empty", "enable", "enact", "end", "endless",
|
||||
"endorse", "enemy", "energy", "enforce", "engage", "engine", "enhance", "enjoy", "enlist", "enough",
|
||||
"enrich", "enroll", "ensure", "enter", "entire", "entry", "envelope", "episode", "equal", "equip",
|
||||
"era", "erase", "erode", "erosion", "error", "erupt", "escape", "essay", "essence", "estate",
|
||||
"eternal", "ethics", "evidence", "evil", "evoke", "evolve", "exact", "example", "excess", "exchange",
|
||||
"excite", "exclude", "excuse", "execute", "exercise", "exhaust", "exhibit", "exile", "exist", "exit",
|
||||
"exotic", "expand", "expect", "expire", "explain", "expose", "express", "extend", "extra", "eye",
|
||||
"eyebrow", "fabric", "face", "faculty", "fade", "faint", "faith", "fall", "false", "fame",
|
||||
"family", "famous", "fan", "fancy", "fantasy", "farm", "fashion", "fat", "fatal", "father",
|
||||
"fatigue", "fault", "favorite", "feature", "february", "federal", "fee", "feed", "feel", "female",
|
||||
"fence", "festival", "fetch", "fever", "few", "fiber", "fiction", "field", "figure", "file",
|
||||
"film", "filter", "final", "find", "fine", "finger", "finish", "fire", "firm", "first",
|
||||
"fiscal", "fish", "fit", "fitness", "fix", "flag", "flame", "flash", "flat", "flavor",
|
||||
"flee", "flight", "flip", "float", "flock", "floor", "flower", "fluid", "flush", "fly",
|
||||
"foam", "focus", "fog", "foil", "fold", "follow", "food", "foot", "force", "forest",
|
||||
"forget", "fork", "fortune", "forum", "forward", "fossil", "foster", "found", "fox", "fragile",
|
||||
"frame", "frequent", "fresh", "friend", "fringe", "frog", "front", "frost", "frown", "frozen",
|
||||
"fruit", "fuel", "fun", "funny", "furnace", "fury", "future", "gadget", "gain", "galaxy",
|
||||
"gallery", "game", "gap", "garage", "garbage", "garden", "garlic", "garment", "gas", "gasp",
|
||||
"gate", "gather", "gauge", "gaze", "general", "genius", "genre", "gentle", "genuine", "gesture",
|
||||
"ghost", "giant", "gift", "giggle", "ginger", "giraffe", "girl", "give", "glad", "glance",
|
||||
"glare", "glass", "glide", "glimpse", "globe", "gloom", "glory", "glove", "glow", "glue",
|
||||
"goat", "goddess", "gold", "good", "goose", "gorilla", "gospel", "gossip", "govern", "gown",
|
||||
"grab", "grace", "grain", "grant", "grape", "grass", "gravity", "great", "green", "grid",
|
||||
"grief", "grit", "grocery", "group", "grow", "grunt", "guard", "guess", "guide", "guilt",
|
||||
"guitar", "gun", "gym", "habit", "hair", "half", "hammer", "hamster", "hand", "happy",
|
||||
"harbor", "hard", "harsh", "harvest", "hat", "have", "hawk", "hazard", "head", "health",
|
||||
"heart", "heavy", "hedgehog", "height", "hello", "helmet", "help", "hen", "hero", "hidden",
|
||||
"high", "hill", "hint", "hip", "hire", "history", "hobby", "hockey", "hold", "hole",
|
||||
"holiday", "hollow", "home", "honey", "hood", "hope", "horn", "horror", "horse", "hospital",
|
||||
"host", "hotel", "hour", "hover", "hub", "huge", "human", "humble", "humor", "hundred",
|
||||
"hungry", "hunt", "hurdle", "hurry", "hurt", "husband", "hybrid", "ice", "icon", "idea",
|
||||
"identify", "idle", "ignore", "ill", "illegal", "illness", "image", "imitate", "immense", "immune",
|
||||
"impact", "impose", "improve", "impulse", "inch", "include", "income", "increase", "index", "indicate",
|
||||
"indoor", "industry", "infant", "inflict", "inform", "inhale", "inherit", "initial", "inject", "injury",
|
||||
"inmate", "inner", "innocent", "input", "inquiry", "insane", "insect", "inside", "inspire", "install",
|
||||
"intact", "interest", "into", "invest", "invite", "involve", "iron", "island", "isolate", "issue",
|
||||
"item", "ivory", "jacket", "jaguar", "jar", "jazz", "jealous", "jeans", "jelly", "jewel",
|
||||
"job", "join", "joke", "journey", "joy", "judge", "juice", "jump", "jungle", "junior",
|
||||
"junk", "just", "kangaroo", "keen", "keep", "ketchup", "key", "kick", "kid", "kidney",
|
||||
"kind", "kingdom", "kiss", "kit", "kitchen", "kite", "kitten", "kiwi", "knee", "knife",
|
||||
"knock", "know", "lab", "label", "labor", "ladder", "lady", "lake", "lamp", "language",
|
||||
"laptop", "large", "later", "latin", "laugh", "laundry", "lava", "law", "lawn", "lawsuit",
|
||||
"layer", "lazy", "leader", "leaf", "learn", "leave", "lecture", "left", "leg", "legal",
|
||||
"legend", "leisure", "lemon", "lend", "length", "lens", "leopard", "lesson", "letter", "level",
|
||||
"liar", "liberty", "library", "license", "life", "lift", "light", "like", "limb", "limit",
|
||||
"link", "lion", "liquid", "list", "little", "live", "lizard", "load", "loan", "lobster",
|
||||
"local", "lock", "logic", "lonely", "long", "loop", "lottery", "loud", "lounge", "love",
|
||||
"loyal", "lucky", "luggage", "lumber", "lunar", "lunch", "luxury", "lyrics", "machine", "mad",
|
||||
"magic", "magnet", "maid", "mail", "main", "major", "make", "mammal", "man", "manage",
|
||||
"mandate", "mango", "mansion", "manual", "maple", "marble", "march", "margin", "marine", "market",
|
||||
"marriage", "mask", "mass", "master", "match", "material", "math", "matrix", "matter", "maximum",
|
||||
"maze", "meadow", "mean", "measure", "meat", "mechanic", "medal", "media", "melody", "melt",
|
||||
"member", "memory", "mention", "menu", "mercy", "merge", "merit", "merry", "mesh", "message",
|
||||
"metal", "method", "middle", "midnight", "milk", "million", "mimic", "mind", "minimum", "minor",
|
||||
"minute", "miracle", "mirror", "misery", "miss", "mistake", "mix", "mixed", "mixture", "mobile",
|
||||
"model", "modify", "mom", "moment", "monitor", "monkey", "monster", "month", "moon", "moral",
|
||||
"more", "morning", "mosquito", "mother", "motion", "motor", "mountain", "mouse", "move", "movie",
|
||||
"much", "muffin", "mule", "multiply", "muscle", "museum", "mushroom", "music", "must", "mutual",
|
||||
"myself", "mystery", "myth", "naive", "name", "napkin", "narrow", "nasty", "nation", "nature",
|
||||
"near", "neck", "need", "negative", "neglect", "neither", "nephew", "nerve", "nest", "net",
|
||||
"network", "neutral", "never", "news", "next", "nice", "night", "noble", "noise", "nominee",
|
||||
"noodle", "normal", "north", "nose", "notable", "note", "nothing", "notice", "novel", "now",
|
||||
"nuclear", "number", "nurse", "nut", "oak", "obey", "object", "oblige", "obscure", "observe",
|
||||
"obtain", "obvious", "occur", "ocean", "october", "odor", "off", "offer", "office", "often",
|
||||
"oil", "okay", "old", "olive", "olympic", "omit", "once", "one", "onion", "online",
|
||||
"only", "open", "opera", "opinion", "oppose", "option", "orange", "orbit", "orchard", "order",
|
||||
"ordinary", "organ", "orient", "original", "orphan", "ostrich", "other", "outdoor", "outer", "output",
|
||||
"outside", "oval", "oven", "over", "own", "owner", "oxygen", "oyster", "ozone", "pact",
|
||||
"paddle", "page", "pair", "palace", "palm", "panda", "panel", "panic", "panther", "paper",
|
||||
"parade", "parent", "park", "parrot", "party", "pass", "patch", "path", "patient", "patrol",
|
||||
"pattern", "pause", "pave", "payment", "peace", "peanut", "pear", "peasant", "pelican", "pen",
|
||||
"penalty", "pencil", "people", "pepper", "perfect", "permit", "person", "pet", "phone", "photo",
|
||||
"phrase", "physical", "piano", "picnic", "picture", "piece", "pig", "pigeon", "pill", "pilot",
|
||||
"pink", "pioneer", "pipe", "pistol", "pitch", "pizza", "place", "planet", "plastic", "plate",
|
||||
"play", "please", "pledge", "pluck", "plug", "plunge", "poem", "poet", "point", "polar",
|
||||
"pole", "police", "pond", "pony", "pool", "popular", "portion", "position", "possible", "post",
|
||||
"potato", "pottery", "poverty", "powder", "power", "practice", "praise", "predict", "prefer", "prepare",
|
||||
"present", "pretty", "prevent", "price", "pride", "primary", "print", "priority", "prison", "private",
|
||||
"prize", "problem", "process", "produce", "profit", "program", "project", "promote", "proof", "property",
|
||||
"prosper", "protect", "proud", "provide", "public", "pudding", "pull", "pulp", "pulse", "pumpkin",
|
||||
"punch", "pupil", "puppy", "purchase", "purity", "purpose", "purse", "push", "put", "puzzle",
|
||||
"pyramid", "quality", "quantum", "quarter", "question", "quick", "quit", "quiz", "quote", "rabbit",
|
||||
"raccoon", "race", "rack", "radar", "radio", "rail", "rain", "raise", "rally", "ramp",
|
||||
"ranch", "random", "range", "rapid", "rare", "rate", "rather", "raven", "raw", "razor",
|
||||
"ready", "real", "reason", "rebel", "rebuild", "recall", "receive", "recipe", "record", "recycle",
|
||||
"reduce", "reflect", "reform", "refuse", "region", "regret", "regular", "reject", "relax", "release",
|
||||
"relief", "rely", "remain", "remember", "remind", "remove", "render", "renew", "rent", "reopen",
|
||||
"repair", "repeat", "replace", "report", "require", "rescue", "resemble", "resist", "resource", "response",
|
||||
"result", "retire", "retreat", "return", "reunion", "reveal", "review", "reward", "rhythm", "rib",
|
||||
"ribbon", "rice", "rich", "ride", "ridge", "rifle", "right", "rigid", "ring", "riot",
|
||||
"ripple", "risk", "ritual", "rival", "river", "road", "roast", "robot", "robust", "rocket",
|
||||
"romance", "roof", "rookie", "room", "rose", "rotate", "rough", "round", "route", "royal",
|
||||
"rubber", "rude", "rug", "rule", "run", "runway", "rural", "sad", "saddle", "sadness",
|
||||
"safe", "sail", "salad", "salmon", "salon", "salt", "salute", "same", "sample", "sand",
|
||||
"satisfy", "satoshi", "sauce", "sausage", "save", "say", "scale", "scan", "scare", "scatter",
|
||||
"scene", "scheme", "school", "science", "scissors", "scorpion", "scout", "scrap", "screen", "script",
|
||||
"scrub", "sea", "search", "season", "seat", "second", "secret", "section", "security", "seed",
|
||||
"seek", "segment", "select", "sell", "seminar", "senior", "sense", "sentence", "series", "service",
|
||||
"session", "settle", "setup", "seven", "shadow", "shaft", "shallow", "share", "shed", "shell",
|
||||
"sheriff", "shield", "shift", "shine", "ship", "shiver", "shock", "shoe", "shoot", "shop",
|
||||
"short", "shoulder", "shove", "shrimp", "shrug", "shuffle", "shy", "sibling", "sick", "side",
|
||||
"siege", "sight", "sign", "silent", "silk", "silly", "silver", "similar", "simple", "since",
|
||||
"sing", "siren", "sister", "situate", "six", "size", "skate", "sketch", "ski", "skill",
|
||||
"skin", "skirt", "skull", "slab", "slam", "sleep", "slender", "slice", "slide", "slight",
|
||||
"slim", "slogan", "slot", "slow", "slush", "small", "smart", "smile", "smoke", "smooth",
|
||||
"snack", "snake", "snap", "sniff", "snow", "soap", "soccer", "social", "sock", "soda",
|
||||
"soft", "solar", "soldier", "solid", "solution", "solve", "someone", "song", "soon", "sorry",
|
||||
"sort", "soul", "sound", "soup", "source", "south", "space", "spare", "spatial", "spawn",
|
||||
"speak", "special", "speed", "spell", "spend", "sphere", "spice", "spider", "spike", "spin",
|
||||
"spirit", "split", "spoil", "sponsor", "spoon", "sport", "spot", "spray", "spread", "spring",
|
||||
"spy", "square", "squeeze", "squirrel", "stable", "stadium", "staff", "stage", "stairs", "stamp",
|
||||
"stand", "start", "state", "stay", "steak", "steel", "stem", "step", "stereo", "stick",
|
||||
"still", "sting", "stock", "stomach", "stone", "stool", "story", "stove", "strategy", "street",
|
||||
"strike", "strong", "struggle", "student", "stuff", "stumble", "style", "subject", "submit", "subway",
|
||||
"success", "such", "sudden", "suffer", "sugar", "suggest", "suit", "summer", "sun", "sunny",
|
||||
"sunset", "super", "supply", "supreme", "sure", "surface", "surge", "surprise", "surround", "survey",
|
||||
"suspect", "sustain", "swallow", "swamp", "swap", "swarm", "swear", "sweet", "swift", "swim",
|
||||
"swing", "switch", "sword", "symbol", "symptom", "syrup", "system", "table", "tackle", "tag",
|
||||
"tail", "talent", "talk", "tank", "tape", "target", "task", "taste", "tattoo", "taxi",
|
||||
"teach", "team", "tell", "ten", "tenant", "tennis", "tent", "term", "test", "text",
|
||||
"thank", "that", "theme", "then", "theory", "there", "they", "thing", "this", "thought",
|
||||
"three", "thrive", "throw", "thumb", "thunder", "ticket", "tide", "tiger", "tilt", "timber",
|
||||
"time", "tiny", "tip", "tired", "tissue", "title", "toast", "tobacco", "today", "toddler",
|
||||
"toe", "together", "toilet", "token", "tomato", "tomorrow", "tone", "tongue", "tonight", "tool",
|
||||
"tooth", "top", "topic", "topple", "torch", "tornado", "tortoise", "toss", "total", "tourist",
|
||||
"toward", "tower", "town", "toy", "track", "trade", "traffic", "tragic", "train", "transfer",
|
||||
"trap", "trash", "travel", "tray", "treat", "tree", "trend", "trial", "tribe", "trick",
|
||||
"trigger", "trim", "trip", "trophy", "trouble", "truck", "true", "truly", "trumpet", "trust",
|
||||
"truth", "try", "tube", "tuition", "tumble", "tuna", "tunnel", "turkey", "turn", "turtle",
|
||||
"twelve", "twenty", "twice", "twin", "twist", "two", "type", "typical", "ugly", "umbrella",
|
||||
"unable", "unaware", "uncle", "uncover", "under", "undo", "unfair", "unfold", "unhappy", "uniform",
|
||||
"unique", "unit", "universe", "unknown", "unlock", "until", "unusual", "unveil", "update", "upgrade",
|
||||
"uphold", "upon", "upper", "upset", "urban", "urge", "usage", "use", "used", "useful",
|
||||
"useless", "usual", "utility", "vacant", "vacuum", "vague", "valid", "valley", "valve", "van",
|
||||
"vanish", "vapor", "various", "vast", "vault", "vehicle", "velvet", "vendor", "venture", "venue",
|
||||
"verb", "verify", "version", "very", "vessel", "veteran", "viable", "vibrant", "vicious", "victory",
|
||||
"video", "view", "village", "vintage", "violin", "virtual", "virus", "visa", "visit", "visual",
|
||||
"vital", "vivid", "vocal", "voice", "void", "volcano", "volume", "vote", "voyage", "wage",
|
||||
"wagon", "wait", "walk", "wall", "walnut", "want", "warfare", "warm", "warrior", "wash",
|
||||
"wasp", "waste", "water", "wave", "way", "wealth", "weapon", "wear", "weasel", "weather",
|
||||
"web", "wedding", "weekend", "weird", "welcome", "west", "wet", "whale", "what", "wheat",
|
||||
"wheel", "when", "where", "whip", "whisper", "wide", "width", "wife", "wild", "will",
|
||||
"win", "window", "wine", "wing", "wink", "winner", "winter", "wire", "wisdom", "wise",
|
||||
"wish", "witness", "wolf", "woman", "wonder", "wood", "wool", "word", "work", "world",
|
||||
"worry", "worth", "wrap", "wreck", "wrestle", "wrist", "write", "wrong", "yard", "year",
|
||||
"yellow", "you", "young", "youth", "zebra", "zero", "zone", "zoo", "zjr",
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package stat
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// FsStats contains data about filesystem usage.
|
||||
// This part of code is taken from k8s.io/kubelet/pkg/apis/stats/v1alpha1
|
||||
// Maybe we should import it directly in the future.
|
||||
type FsStats struct {
|
||||
// The time at which these stats were updated.
|
||||
Time metav1.Time `json:"time"`
|
||||
// AvailableBytes represents the storage space available (bytes) for the filesystem.
|
||||
// +optional
|
||||
AvailableBytes *uint64 `json:"availableBytes,omitempty"`
|
||||
// CapacityBytes represents the total capacity (bytes) of the filesystems underlying storage.
|
||||
// +optional
|
||||
CapacityBytes *uint64 `json:"capacityBytes,omitempty"`
|
||||
// UsedBytes represents the bytes used for a specific task on the filesystem.
|
||||
// This may differ from the total bytes used on the filesystem and may not equal CapacityBytes - AvailableBytes.
|
||||
// e.g. For ContainerStats.Rootfs this is the bytes used by the container rootfs on the filesystem.
|
||||
// +optional
|
||||
UsedBytes *uint64 `json:"usedBytes,omitempty"`
|
||||
// InodesFree represents the free inodes in the filesystem.
|
||||
// +optional
|
||||
InodesFree *uint64 `json:"inodesFree,omitempty"`
|
||||
// Inodes represents the total inodes in the filesystem.
|
||||
// +optional
|
||||
Inodes *uint64 `json:"inodes,omitempty"`
|
||||
// InodesUsed represents the inodes used by the filesystem
|
||||
// This may not equal Inodes - InodesFree because this filesystem may share inodes with other "filesystems"
|
||||
// e.g. For ContainerStats.Rootfs, this is the inodes used only by that container, and does not count inodes used by other containers.
|
||||
InodesUsed *uint64 `json:"inodesUsed,omitempty"`
|
||||
}
|
||||
|
||||
type NodeStatsProvider interface {
|
||||
ContainerFsStats(ctx context.Context) (FsStats, error)
|
||||
}
|
||||
type NodeStatsProviderImpl struct {
|
||||
// Client *containerd.Client
|
||||
}
|
||||
|
||||
func (n *NodeStatsProviderImpl) ContainerFsStats(ctx context.Context) (FsStats, error) {
|
||||
// This is a placeholder for the actual implementation.
|
||||
// In a real implementation, this would return the filesystem stats of the container.
|
||||
availableBytes := uint64(100000000000) // Example value
|
||||
capacityBytes := uint64(200000000000) // Example value
|
||||
return FsStats{
|
||||
AvailableBytes: &availableBytes, // Example value
|
||||
CapacityBytes: &capacityBytes, // Example value
|
||||
}, nil
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
// Copyright © 2024 sealos.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package label
|
||||
|
||||
type Recommended struct {
|
||||
Name string
|
||||
Instance string
|
||||
Version string
|
||||
Component string
|
||||
PartOf string
|
||||
ManagedBy string
|
||||
}
|
||||
|
||||
const (
|
||||
AppName AppKey = "app.kubernetes.io/name"
|
||||
AppInstance AppKey = "app.kubernetes.io/instance"
|
||||
AppVersion AppKey = "app.kubernetes.io/version"
|
||||
AppComponent AppKey = "app.kubernetes.io/component"
|
||||
AppPartOf AppKey = "app.kubernetes.io/part-of"
|
||||
AppManagedBy AppKey = "app.kubernetes.io/managed-by"
|
||||
)
|
||||
|
||||
type AppKey = string
|
||||
|
||||
const (
|
||||
DefaultManagedBy = "sealos"
|
||||
)
|
||||
|
||||
func (r *Recommended) Labels() map[string]string {
|
||||
ret := map[string]string{}
|
||||
|
||||
if r.Name != "" {
|
||||
ret[AppName] = r.Name
|
||||
}
|
||||
if r.Instance != "" {
|
||||
ret[AppInstance] = r.Instance
|
||||
}
|
||||
if r.Version != "" {
|
||||
ret[AppVersion] = r.Version
|
||||
}
|
||||
if r.Component != "" {
|
||||
ret[AppComponent] = r.Component
|
||||
}
|
||||
if r.PartOf != "" {
|
||||
ret[AppPartOf] = r.PartOf
|
||||
}
|
||||
if r.ManagedBy != "" {
|
||||
ret[AppManagedBy] = r.ManagedBy
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func RecommendedLabels(r *Recommended) map[string]string {
|
||||
return r.Labels()
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
// Run e2e tests using the Ginkgo runner.
|
||||
func TestE2E(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
_, _ = fmt.Fprintf(GinkgoWriter, "Starting devbox suite\n")
|
||||
RunSpecs(t, "e2e suite")
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/labring/sealos/controllers/devbox/test/utils"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
const namespace = "devbox-system"
|
||||
|
||||
var _ = Describe("controller", Ordered, func() {
|
||||
BeforeAll(func() {
|
||||
By("installing prometheus operator")
|
||||
Expect(utils.InstallPrometheusOperator()).To(Succeed())
|
||||
|
||||
By("installing the cert-manager")
|
||||
Expect(utils.InstallCertManager()).To(Succeed())
|
||||
|
||||
By("creating manager namespace")
|
||||
cmd := exec.Command("kubectl", "create", "ns", namespace)
|
||||
_, _ = utils.Run(cmd)
|
||||
})
|
||||
|
||||
AfterAll(func() {
|
||||
By("uninstalling the Prometheus manager bundle")
|
||||
utils.UninstallPrometheusOperator()
|
||||
|
||||
By("uninstalling the cert-manager bundle")
|
||||
utils.UninstallCertManager()
|
||||
|
||||
By("removing manager namespace")
|
||||
cmd := exec.Command("kubectl", "delete", "ns", namespace)
|
||||
_, _ = utils.Run(cmd)
|
||||
})
|
||||
|
||||
Context("Operator", func() {
|
||||
It("should run successfully", func() {
|
||||
var controllerPodName string
|
||||
var err error
|
||||
|
||||
// projectimage stores the name of the image used in the example
|
||||
projectimage := "example.com/devbox:v0.0.1"
|
||||
|
||||
By("building the manager(Operator) image")
|
||||
cmd := exec.Command("make", "docker-build", "IMG="+projectimage)
|
||||
_, err = utils.Run(cmd)
|
||||
ExpectWithOffset(1, err).NotTo(HaveOccurred())
|
||||
|
||||
By("loading the the manager(Operator) image on Kind")
|
||||
err = utils.LoadImageToKindClusterWithName(projectimage)
|
||||
ExpectWithOffset(1, err).NotTo(HaveOccurred())
|
||||
|
||||
By("installing CRDs")
|
||||
cmd = exec.Command("make", "install")
|
||||
_, err = utils.Run(cmd)
|
||||
ExpectWithOffset(1, err).NotTo(HaveOccurred())
|
||||
|
||||
By("deploying the controller-manager")
|
||||
cmd = exec.Command("make", "deploy", "IMG="+projectimage)
|
||||
_, err = utils.Run(cmd)
|
||||
ExpectWithOffset(1, err).NotTo(HaveOccurred())
|
||||
|
||||
By("validating that the controller-manager pod is running as expected")
|
||||
verifyControllerUp := func() error {
|
||||
// Get pod name
|
||||
|
||||
cmd = exec.Command("kubectl", "get",
|
||||
"pods", "-l", "control-plane=controller-manager",
|
||||
"-o", "go-template={{ range .items }}"+
|
||||
"{{ if not .metadata.deletionTimestamp }}"+
|
||||
"{{ .metadata.name }}"+
|
||||
"{{ \"\\n\" }}{{ end }}{{ end }}",
|
||||
"-n", namespace,
|
||||
)
|
||||
|
||||
podOutput, err := utils.Run(cmd)
|
||||
ExpectWithOffset(2, err).NotTo(HaveOccurred())
|
||||
podNames := utils.GetNonEmptyLines(string(podOutput))
|
||||
if len(podNames) != 1 {
|
||||
return fmt.Errorf("expect 1 controller pods running, but got %d", len(podNames))
|
||||
}
|
||||
controllerPodName = podNames[0]
|
||||
ExpectWithOffset(
|
||||
2,
|
||||
controllerPodName,
|
||||
).Should(ContainSubstring("controller-manager"))
|
||||
|
||||
// Validate pod status
|
||||
cmd = exec.Command("kubectl", "get",
|
||||
"pods", controllerPodName, "-o", "jsonpath={.status.phase}",
|
||||
"-n", namespace,
|
||||
)
|
||||
status, err := utils.Run(cmd)
|
||||
ExpectWithOffset(2, err).NotTo(HaveOccurred())
|
||||
if string(status) != "Running" {
|
||||
return fmt.Errorf("controller pod in %s status", status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
EventuallyWithOffset(1, verifyControllerUp, time.Minute, time.Second).Should(Succeed())
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
v2 "github.com/onsi/ginkgo/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
prometheusOperatorVersion = "v0.72.0"
|
||||
prometheusOperatorURL = "https://github.com/prometheus-operator/prometheus-operator/" +
|
||||
"releases/download/%s/bundle.yaml"
|
||||
|
||||
certmanagerVersion = "v1.14.4"
|
||||
certmanagerURLTmpl = "https://github.com/jetstack/cert-manager/releases/download/%s/cert-manager.yaml"
|
||||
)
|
||||
|
||||
func warnError(err error) {
|
||||
_, _ = fmt.Fprintf(v2.GinkgoWriter, "warning: %v\n", err)
|
||||
}
|
||||
|
||||
// InstallPrometheusOperator installs the prometheus Operator to be used to export the enabled metrics.
|
||||
func InstallPrometheusOperator() error {
|
||||
url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion)
|
||||
cmd := exec.Command("kubectl", "create", "-f", url)
|
||||
_, err := Run(cmd)
|
||||
return err
|
||||
}
|
||||
|
||||
// Run executes the provided command within this context
|
||||
func Run(cmd *exec.Cmd) ([]byte, error) {
|
||||
dir, _ := GetProjectDir()
|
||||
cmd.Dir = dir
|
||||
|
||||
if err := os.Chdir(cmd.Dir); err != nil {
|
||||
_, _ = fmt.Fprintf(v2.GinkgoWriter, "chdir dir: %s\n", err)
|
||||
}
|
||||
|
||||
cmd.Env = append(os.Environ(), "GO111MODULE=on")
|
||||
command := strings.Join(cmd.Args, " ")
|
||||
_, _ = fmt.Fprintf(v2.GinkgoWriter, "running: %s\n", command)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return output, fmt.Errorf("%s failed with error: %w %s", command, err, string(output))
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// UninstallPrometheusOperator uninstalls the prometheus
|
||||
func UninstallPrometheusOperator() {
|
||||
url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion)
|
||||
cmd := exec.Command("kubectl", "delete", "-f", url)
|
||||
if _, err := Run(cmd); err != nil {
|
||||
warnError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// UninstallCertManager uninstalls the cert manager
|
||||
func UninstallCertManager() {
|
||||
url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)
|
||||
cmd := exec.Command("kubectl", "delete", "-f", url)
|
||||
if _, err := Run(cmd); err != nil {
|
||||
warnError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// InstallCertManager installs the cert manager bundle.
|
||||
func InstallCertManager() error {
|
||||
url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)
|
||||
cmd := exec.Command("kubectl", "apply", "-f", url)
|
||||
if _, err := Run(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
// Wait for cert-manager-webhook to be ready, which can take time if cert-manager
|
||||
// was re-installed after uninstalling on a cluster.
|
||||
cmd = exec.Command("kubectl", "wait", "deployment.apps/cert-manager-webhook",
|
||||
"--for", "condition=Available",
|
||||
"--namespace", "cert-manager",
|
||||
"--timeout", "5m",
|
||||
)
|
||||
|
||||
_, err := Run(cmd)
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadImageToKindClusterWithName loads a local docker image to the kind cluster
|
||||
func LoadImageToKindClusterWithName(name string) error {
|
||||
cluster := "kind"
|
||||
if v, ok := os.LookupEnv("KIND_CLUSTER"); ok {
|
||||
cluster = v
|
||||
}
|
||||
kindOptions := []string{"load", "docker-image", name, "--name", cluster}
|
||||
cmd := exec.Command("kind", kindOptions...)
|
||||
_, err := Run(cmd)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetNonEmptyLines converts given command output string into individual objects
|
||||
// according to line breakers, and ignores the empty elements in it.
|
||||
func GetNonEmptyLines(output string) []string {
|
||||
var res []string
|
||||
elements := strings.Split(output, "\n")
|
||||
for _, element := range elements {
|
||||
if element != "" {
|
||||
res = append(res, element)
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// GetProjectDir will return the directory where the project is
|
||||
func GetProjectDir() (string, error) {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return wd, err
|
||||
}
|
||||
wd = strings.ReplaceAll(wd, "/test/e2e", "")
|
||||
return wd, nil
|
||||
}
|
||||
@@ -3,7 +3,6 @@ go 1.25.0
|
||||
use (
|
||||
./account
|
||||
./app
|
||||
./devbox
|
||||
./job/heartbeat
|
||||
./job/init
|
||||
./license
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
|
||||
bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM=
|
||||
cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg=
|
||||
cel.dev/expr v0.16.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg=
|
||||
cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo=
|
||||
cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
|
||||
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
@@ -443,6 +445,7 @@ cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGB
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
|
||||
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||
cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
|
||||
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
|
||||
@@ -1786,6 +1789,7 @@ git.sr.ht/~sbinet/gg v0.3.1 h1:LNhjNn8DerC8f9DHLz6lS0YYul/b602DUxDgGkd/Aik=
|
||||
git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2 h1:dIScnXFlF784X79oi7MzVT6GWqr/W1uUt0pB5CsDs9M=
|
||||
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2/go.mod h1:gCLVsLfv1egrcZu+GoJATN5ts75F2s62ih/457eWzOw=
|
||||
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
|
||||
@@ -1975,6 +1979,8 @@ github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwc
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220418222510-f25a4f6275ed h1:ue9pVfIcP+QMEjfgo/Ez4ZjNZfonGgR6NgjMaJMu1Cg=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220418222510-f25a4f6275ed/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
|
||||
github.com/apache/arrow/go/v10 v10.0.1 h1:n9dERvixoC/1JjDmBcs9FPaEryoANa2sCgVFo6ez9cI=
|
||||
github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0=
|
||||
github.com/apache/arrow/go/v11 v11.0.0 h1:hqauxvFQxww+0mEU/2XHG6LT7eZternCZq+A5Yly2uM=
|
||||
@@ -1999,6 +2005,7 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26
|
||||
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/authzed/controller-idioms v0.7.0 h1:HhNMUBb8hJzYqY3mhen3B2AC5nsIem3fBe0tC/AAOHo=
|
||||
@@ -2169,6 +2176,7 @@ github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq
|
||||
github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido69INQaVO6d87Qn543Xr6nooe9Kz7oBFM=
|
||||
github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw=
|
||||
github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k=
|
||||
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
@@ -2397,6 +2405,7 @@ github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
|
||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 h1:Lgdd/Qp96Qj8jqLpq2cI1I1X7BJnu06efS+XkhRoLUQ=
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20230514072755-504adb8a8af1/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw=
|
||||
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
|
||||
@@ -2521,6 +2530,7 @@ github.com/envoyproxy/go-control-plane v0.11.1 h1:wSUXTlLfiAQRWs2F+p+EKOY9rUyis1
|
||||
github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g=
|
||||
github.com/envoyproxy/go-control-plane v0.12.0 h1:4X+VP1GHd1Mhj6IB5mMeGbLCleqxjletLK6K0rbxyZI=
|
||||
github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0=
|
||||
github.com/envoyproxy/go-control-plane v0.13.0/go.mod h1:GRaKG3dwvFoTg4nj7aXdZnvMg4d7nvT/wl9WgVXn3Q8=
|
||||
github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
|
||||
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
|
||||
@@ -2537,6 +2547,7 @@ github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBF
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
|
||||
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
@@ -2722,6 +2733,7 @@ github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP
|
||||
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||
github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4=
|
||||
github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||
github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||
github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc=
|
||||
github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@@ -2740,6 +2752,8 @@ github.com/google/cel-go v0.12.6 h1:kjeKudqV0OygrAqA9fX6J55S8gj+Jre2tckIm5RoG4M=
|
||||
github.com/google/cel-go v0.12.6/go.mod h1:Jk7ljRzLBhkmiAwBoUxB1sZSCVBAzkqPF25olK/iRDw=
|
||||
github.com/google/cel-go v0.16.0/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY=
|
||||
github.com/google/cel-go v0.16.1/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY=
|
||||
github.com/google/cel-go v0.22.0 h1:b3FJZxpiv1vTMo2/5RDUqAHPxkT8mmMfJIrq1llbf7g=
|
||||
github.com/google/cel-go v0.22.0/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8=
|
||||
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/flatbuffers v2.0.8+incompatible h1:ivUb1cGomAB101ZM1T0nOiWz9pSrTMoa9+EiY7igmkM=
|
||||
github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
@@ -2748,6 +2762,8 @@ github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZat
|
||||
github.com/google/fswalker v0.2.1-0.20200214223026-f0e929ba4126/go.mod h1:ZSEBqY0IHKqWPeAbTyvccv9bb9vCnaQfHe31cm911Ng=
|
||||
github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
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/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0=
|
||||
github.com/google/go-containerregistry v0.15.2/go.mod h1:wWK+LnOv4jXMM23IT/F1wdYftGWGr47Is8CG+pmHK1Q=
|
||||
github.com/google/go-containerregistry v0.20.1 h1:eTgx9QNYugV4DN5mz4U8hiAGTi1ybXn0TPi4Smd8du0=
|
||||
@@ -2874,6 +2890,7 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4Zs
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 h1:lLT7ZLSzGLI08vc9cpd+tYmNWjdKDqyr/2L+f6U12Fk=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
|
||||
github.com/hanwen/go-fuse/v2 v2.1.1-0.20220112183258-f57e95bda82d/go.mod h1:B1nGE/6RBFyBRC1RRnf23UpwCdyJ31eukw34oAKukAc=
|
||||
@@ -2907,6 +2924,7 @@ github.com/hashicorp/go-kms-wrapping/v2 v2.0.8/go.mod h1:qTCjxGig/kjuj3hk1z8pOUr
|
||||
github.com/hashicorp/go-memdb v1.3.2 h1:RBKHOsnSszpU6vxq80LzC2BaQjuuvoyaQbkLTf7V7g8=
|
||||
github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4=
|
||||
github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-plugin v1.4.8 h1:CHGwpxYDOttQOY7HOWgETU9dyVjOXzniXDqJcYJE1zM=
|
||||
github.com/hashicorp/go-plugin v1.4.8/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3 h1:QlWt0KvWT0lq8MFppF9tsJGF+ynG7ztc2KIPhzRGk7s=
|
||||
@@ -2990,6 +3008,7 @@ github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK
|
||||
github.com/imdario/mergo v0.3.14/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab h1:HqW4xhhynfjrtEiiSGcQUd6vrK23iMam1FO8rI7mwig=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
|
||||
@@ -3178,6 +3197,7 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
@@ -3237,6 +3257,7 @@ github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mndrix/tap-go v0.0.0-20171203230836-629fa407e90b h1:Ga1nclDSe8gOw37MVLMhfu2QKWtD6gvtQ298zsKVh8g=
|
||||
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
||||
github.com/moby/patternmatcher v0.5.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
|
||||
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
|
||||
@@ -3244,10 +3265,12 @@ github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU
|
||||
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
|
||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||
github.com/moby/sys/mount v0.3.4/go.mod h1:KcQJMbQdJHPlq5lcYT+/CjatWM4PuxKe+XLSVS4J6Os=
|
||||
github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
|
||||
github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
|
||||
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
|
||||
github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
||||
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
|
||||
github.com/moby/sys/reexec v0.1.0 h1:RrBi8e0EBTLEgfruBOFcxtElzRGTEUkeIFaVXgU7wok=
|
||||
github.com/moby/sys/reexec v0.1.0/go.mod h1:EqjBg8F3X7iZe5pU6nRZnYCMUTXoxsjiIfHup5wYIN8=
|
||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||
@@ -3273,6 +3296,7 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P
|
||||
github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
|
||||
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||
github.com/mrunalp/fileutils v0.5.0 h1:NKzVxiH7eSk+OQ4M+ZYW1K6h27RUV3MI6NUTsHhU6Z4=
|
||||
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
||||
github.com/mrunalp/fileutils v0.5.1 h1:F+S7ZlNKnrwHfSwdlgNSkKo67ReVf8o9fel6C3dkm/Q=
|
||||
github.com/mrunalp/fileutils v0.5.1/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
||||
github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA=
|
||||
@@ -3543,6 +3567,7 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
|
||||
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
|
||||
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
|
||||
github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245 h1:K1Xf3bKttbF+koVGaX5xngRIZ5bVjbmPnaxE/dR08uY=
|
||||
github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk=
|
||||
@@ -3635,6 +3660,7 @@ github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUq
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
@@ -3657,6 +3683,8 @@ github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8W
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
|
||||
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec h1:q6XVwXmKvCRHRqesF3cSv6lNqqHi0QWOvgDlSohg8UA=
|
||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
|
||||
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
|
||||
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
|
||||
github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo=
|
||||
github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e h1:mOtuXaRAbVZsxAHVdPR3IjfmN8T1h2iczJLynhLybf8=
|
||||
github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -3667,6 +3695,7 @@ github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRci
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/studio-b12/gowebdav v0.0.0-20211106090535-29e74efa701f/go.mod h1:gCcfDlA1Y7GqOaeEKw5l9dOGx1VLdc/HuQSlQAaZ30s=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
@@ -3763,9 +3792,13 @@ github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr
|
||||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xhit/go-str2duration v1.2.0 h1:BcV5u025cITWxEQKGWr1URRzrcXtu7uk8+luz3Yuhwc=
|
||||
github.com/xhit/go-str2duration v1.2.0/go.mod h1:3cPSlfZlUHVlneIVfePFWcJZsuwf+P1v2SRTV4cUmp4=
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
@@ -3907,6 +3940,8 @@ go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352/go.mod h1:SNgMg+EgDFwmvS
|
||||
go.mozilla.org/pkcs7 v0.9.0 h1:yM4/HS9dYv7ri2biPtxt8ikvB37a980dg69/pKmS+eI=
|
||||
go.mozilla.org/pkcs7 v0.9.0/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib v0.20.0 h1:ubFQUn0VCZ0gPwIoJfBJVpeBlyRMxu8Mm/huKWYd9p0=
|
||||
go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo=
|
||||
@@ -3986,6 +4021,7 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0/go.mod h1:HrbCVv40OOLT
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 h1:DeFD0VgTZ+Cj6hxravYYZE2W4GlneVH81iAOPjZkzk8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0/go.mod h1:GijYcYmNpX1KazD5JmWGsi4P7dDTTTnfv1UbGn84MnU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.0.1/go.mod h1:xOvWoTOrQjxjW61xtOmD/WKGRYb/P4NzRo3bs65U6Rk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY=
|
||||
@@ -3995,6 +4031,7 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.2/go.mod h
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0/go.mod h1:5w41DY6S9gZrbjuq6Y+753e96WfPha5IcsOSZTtullM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 h1:gvmNvqrPYovvyRmCSygkUDyL8lC5Tl845MLEwqpxhEU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0/go.mod h1:vNUq47TGFioo+ffTSnKNdob241vePmtNZnAODKapKd0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0/go.mod h1:QNX1aly8ehqqX1LEa6YniTU7VY9I6R3X/oPxhGdTceE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.14.0/go.mod h1:+N7zNjIJv4K+DeX67XXET0P+eIciESgaFDBqh+ZJFS4=
|
||||
@@ -4043,6 +4080,7 @@ go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwP
|
||||
go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
|
||||
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
||||
go.opentelemetry.io/otel/sdk/export/metric v0.20.0 h1:c5VRjxCXdQlx1HjzwGdQHzZaVI82b5EbBgOu2ljD92g=
|
||||
go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE=
|
||||
@@ -4081,6 +4119,7 @@ go.opentelemetry.io/proto/otlp v0.16.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI
|
||||
go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw=
|
||||
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
||||
go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY=
|
||||
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
|
||||
@@ -4385,6 +4424,7 @@ golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
@@ -4763,6 +4803,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 h1:YcyjlL1PRr2Q17/I0dPk2JmYS5CDXfcdb2Z3YRioEbw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250102185135-69823020774d/go.mod h1:2v7Z7gP2ZUOGsaFyxATQSRoBnKygqVq2Cwnvom7QiqY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250204164813-702378808489/go.mod h1:iYONQfRdizDB8JJBybql13nArx91jcUk7zCXEsOofM4=
|
||||
@@ -4821,8 +4863,11 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240823204242-4ba0660f739c/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
|
||||
@@ -4881,6 +4926,8 @@ google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDom
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||
google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw=
|
||||
google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
||||
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
||||
google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw=
|
||||
google.golang.org/grpc v1.69.0/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
||||
@@ -4936,6 +4983,7 @@ gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||
gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY=
|
||||
gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
helm.sh/helm/v3 v3.12.3 h1:5y1+Sbty12t48T/t/CGNYUIME5BJ0WKfmW/sobYqkFg=
|
||||
helm.sh/helm/v3 v3.12.3/go.mod h1:KPKQiX9IP5HX7o5YnnhViMnNuKiL/lJBVQ47GHe1R0k=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
@@ -4952,6 +5000,7 @@ k8s.io/apiserver v0.26.2/go.mod h1:GHcozwXgXsPuOJ28EnQ/jXEM9QeG6HT22YxSNmpYNh8=
|
||||
k8s.io/apiserver v0.26.3/go.mod h1:CJe/VoQNcXdhm67EvaVjYXxR3QyfwpceKPuPaeLibTA=
|
||||
k8s.io/apiserver v0.28.1/go.mod h1:d8aizlSRB6yRgJ6PKfDkdwCy2DXt/d1FDR6iJN9kY1w=
|
||||
k8s.io/apiserver v0.28.3/go.mod h1:YIpM+9wngNAv8Ctt0rHG4vQuX/I5rvkEMtZtsxW2rNM=
|
||||
k8s.io/apiserver v0.32.1 h1:oo0OozRos66WFq87Zc5tclUX2r0mymoVHRq8JmR7Aak=
|
||||
k8s.io/apiserver v0.32.1/go.mod h1:UcB9tWjBY7aryeI5zAgzVJB/6k7E97bkr1RgqDz0jPw=
|
||||
k8s.io/cli-runtime v0.28.2 h1:64meB2fDj10/ThIMEJLO29a1oujSm0GQmKzh1RtA/uk=
|
||||
k8s.io/cli-runtime v0.28.2/go.mod h1:bTpGOvpdsPtDKoyfG4EG041WIyFZLV9qq4rPlkyYfDA=
|
||||
@@ -5143,6 +5192,8 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.35 h1:+xBL5uTc+BkPB
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.35/go.mod h1:WxjusMwXlKzfAs4p9km6XJRndVt2FROgMVCE4cdohFo=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.36/go.mod h1:WxjusMwXlKzfAs4p9km6XJRndVt2FROgMVCE4cdohFo=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2/go.mod h1:+qG7ISXqCDVVcyO8hLn12AKVYYUjM7ftlqsqmrhMZE0=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 h1:CPT0ExVicCzcpeN4baWEV2ko2Z/AsiZgEdwgcfwLgMo=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
|
||||
sigs.k8s.io/controller-tools v0.6.2 h1:+Y8L0UsAugDipGRw8lrkPoAi6XqlQVZuf1DQHME3PgU=
|
||||
sigs.k8s.io/gateway-api v0.4.0 h1:07IJkTt21NetZTHtPKJk2I4XIgDN4BAlTIq1wK7V11o=
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
|
||||
@@ -27,7 +27,6 @@ declare -A CONTROLLER_MODULES=(
|
||||
["job-heartbeat"]="job/heartbeat"
|
||||
["resources"]="resources"
|
||||
["node"]="node"
|
||||
["devbox"]="devbox"
|
||||
["objectstorage"]="objectstorage"
|
||||
)
|
||||
|
||||
@@ -38,10 +37,8 @@ declare -A SERVICE_MODULES=(
|
||||
["minio"]="minio"
|
||||
["launchpad"]="launchpad"
|
||||
["exceptionmonitor"]="exceptionmonitor"
|
||||
["devbox"]="devbox"
|
||||
["vlogs"]="vlogs"
|
||||
["hubble"]="hubble"
|
||||
["sshgate"]="sshgate"
|
||||
)
|
||||
|
||||
# Function to get all modules for a type
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
target/
|
||||
.claude/
|
||||
**/*.rs.bk
|
||||
dhat-heap.json
|
||||
.vscode
|
||||
.idea
|
||||
.cover
|
||||
bleeper.user.toml
|
||||
@@ -1 +0,0 @@
|
||||
edition = "2021"
|
||||
Generated
-3043
File diff suppressed because it is too large
Load Diff
@@ -1,11 +0,0 @@
|
||||
|
||||
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["httpgate"]
|
||||
|
||||
[workspace.dependencies]
|
||||
|
||||
[profile.bench]
|
||||
debug = true
|
||||
@@ -1,3 +0,0 @@
|
||||
target/
|
||||
Dockerfile
|
||||
.github/
|
||||
@@ -1,8 +0,0 @@
|
||||
target/
|
||||
.claude/
|
||||
**/*.rs.bk
|
||||
dhat-heap.json
|
||||
.vscode
|
||||
.idea
|
||||
.cover
|
||||
bleeper.user.toml
|
||||
Generated
-3032
File diff suppressed because it is too large
Load Diff
@@ -1,42 +0,0 @@
|
||||
[package]
|
||||
name = "httpgate"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
# Pingora HTTP proxy
|
||||
pingora-core = "0.6"
|
||||
pingora-proxy = "0.6"
|
||||
pingora-http = "0.6"
|
||||
|
||||
# Kubernetes
|
||||
kube = { version = "2.0", features = ["runtime", "derive"] }
|
||||
k8s-openapi = { version = "0.26", features = ["v1_32"] }
|
||||
|
||||
# Serialization (for CRD)
|
||||
schemars = "1.1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
|
||||
# Async runtime
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "time", "sync"] }
|
||||
async-trait = "0.1"
|
||||
futures = "0.3"
|
||||
|
||||
# Utilities
|
||||
bytes = "1"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
regex = "1"
|
||||
dashmap = "6"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
debug = 0
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
incremental = false
|
||||
panic = "unwind"
|
||||
strip = "symbols"
|
||||
debug-assertions = false
|
||||
overflow-checks = false
|
||||
@@ -1,33 +0,0 @@
|
||||
FROM rust:alpine AS builder
|
||||
|
||||
RUN apk add \
|
||||
musl-dev \
|
||||
make \
|
||||
cmake \
|
||||
g++
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy workspace root files
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
|
||||
# Copy the target crate
|
||||
COPY httpgate ./httpgate
|
||||
|
||||
# Build the specific package
|
||||
RUN cargo build --release --package httpgate
|
||||
|
||||
FROM alpine
|
||||
|
||||
# Run as non-root user
|
||||
RUN adduser -D -u 1000 httpgate
|
||||
USER httpgate
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the binary from builder
|
||||
COPY --from=builder /app/target/release/httpgate /app/httpgate
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["/app/httpgate"]
|
||||
@@ -1,6 +0,0 @@
|
||||
FROM scratch
|
||||
COPY registry registry
|
||||
COPY install.sh install.sh
|
||||
COPY charts charts
|
||||
|
||||
CMD ["bash install.sh"]
|
||||
@@ -1,23 +0,0 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
@@ -1,15 +0,0 @@
|
||||
apiVersion: v2
|
||||
name: httpgate
|
||||
description: A Kubernetes-native HTTP gateway that routes HTTP requests to Devbox pods based on Host header
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "latest"
|
||||
keywords:
|
||||
- http
|
||||
- gateway
|
||||
- devbox
|
||||
- sealos
|
||||
- proxy
|
||||
- pingora
|
||||
maintainers:
|
||||
- name: httpgate
|
||||
@@ -1,62 +0,0 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "httpgate.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "httpgate.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "httpgate.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "httpgate.labels" -}}
|
||||
helm.sh/chart: {{ include "httpgate.chart" . }}
|
||||
{{ include "httpgate.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "httpgate.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "httpgate.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "httpgate.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "httpgate.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -1,17 +0,0 @@
|
||||
{{- if .Values.rbac.create -}}
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: {{ include "httpgate.fullname" . }}
|
||||
labels:
|
||||
{{- include "httpgate.labels" . | nindent 4 }}
|
||||
rules:
|
||||
# Watch Pods to get Pod IPs
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
# Watch Devbox CRDs to get uniqueID mappings
|
||||
- apiGroups: ["devbox.sealos.io"]
|
||||
resources: ["devboxes"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
{{- end }}
|
||||
@@ -1,16 +0,0 @@
|
||||
{{- if .Values.rbac.create -}}
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: {{ include "httpgate.fullname" . }}
|
||||
labels:
|
||||
{{- include "httpgate.labels" . | nindent 4 }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: {{ include "httpgate.fullname" . }}
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ include "httpgate.serviceAccountName" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
{{- end }}
|
||||
@@ -1,16 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "httpgate.fullname" . }}
|
||||
labels:
|
||||
{{- include "httpgate.labels" . | nindent 4 }}
|
||||
data:
|
||||
{{- if not (hasKey .Values.env "LISTEN_ADDR") }}
|
||||
LISTEN_ADDR: {{ printf "0.0.0.0:%d" (int .Values.httpPort) | quote }}
|
||||
{{- end }}
|
||||
{{- if not (hasKey .Values.env "AGENT_PORT") }}
|
||||
AGENT_PORT: {{ .Values.agentPort | quote }}
|
||||
{{- end }}
|
||||
{{- range $key, $value := .Values.env }}
|
||||
{{ $key }}: {{ $value | quote }}
|
||||
{{- end }}
|
||||
@@ -1,73 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: {{ include "httpgate.fullname" . }}
|
||||
labels:
|
||||
{{- include "httpgate.labels" . | nindent 4 }}
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "httpgate.selectorLabels" . | nindent 6 }}
|
||||
updateStrategy:
|
||||
type: {{ .Values.updateStrategy.type }}
|
||||
{{- if eq .Values.updateStrategy.type "RollingUpdate" }}
|
||||
rollingUpdate:
|
||||
maxUnavailable: {{ .Values.updateStrategy.rollingUpdate.maxUnavailable }}
|
||||
{{- end }}
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
|
||||
{{- with .Values.podAnnotations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "httpgate.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "httpgate.serviceAccountName" . }}
|
||||
{{- with .Values.priorityClassName }}
|
||||
priorityClassName: {{ . }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.httpPort }}
|
||||
protocol: TCP
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "httpgate.fullname" . }}
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.httpPort }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.httpPort }}
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
@@ -1,113 +0,0 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
---
|
||||
# HTTP Ingress (devbox- prefix, non-gRPC requests)
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ include "httpgate.fullname" . }}
|
||||
labels:
|
||||
{{- include "httpgate.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
|
||||
higress.io/prefix-match-header-host: devbox-
|
||||
{{- with .Values.ingress.annotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .Values.ingress.ingressClassName }}
|
||||
ingressClassName: {{ .Values.ingress.ingressClassName }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
{{- $host := .host }}
|
||||
{{- if not (hasPrefix "*." $host) }}
|
||||
{{- $host = printf "*.%s" $host }}
|
||||
{{- end }}
|
||||
- host: {{ $host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- $paths := .paths | default (list (dict "path" "/" "pathType" "Prefix")) }}
|
||||
{{- range $paths }}
|
||||
- path: {{ .path }}
|
||||
pathType: {{ .pathType }}
|
||||
backend:
|
||||
service:
|
||||
name: {{ include "httpgate.fullname" $ }}
|
||||
port:
|
||||
number: {{ $.Values.httpPort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
{{- $tlsHost := . }}
|
||||
{{- if not (hasPrefix "*." $tlsHost) }}
|
||||
{{- $tlsHost = printf "*.%s" $tlsHost }}
|
||||
{{- end }}
|
||||
- {{ $tlsHost | quote }}
|
||||
{{- end }}
|
||||
{{- if .secretName }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
---
|
||||
# gRPC Ingress (devbox- prefix, gRPC requests detected by content-type)
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ include "httpgate.fullname" . }}-grpc
|
||||
labels:
|
||||
{{- include "httpgate.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
|
||||
higress.io/prefix-match-header-host: devbox-
|
||||
higress.io/prefix-match-header-content-type: application/grpc
|
||||
{{- with .Values.ingress.annotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .Values.ingress.ingressClassName }}
|
||||
ingressClassName: {{ .Values.ingress.ingressClassName }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
{{- $host := .host }}
|
||||
{{- if not (hasPrefix "*." $host) }}
|
||||
{{- $host = printf "*.%s" $host }}
|
||||
{{- end }}
|
||||
- host: {{ $host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- $paths := .paths | default (list (dict "path" "/" "pathType" "Prefix")) }}
|
||||
{{- range $paths }}
|
||||
- path: {{ .path }}
|
||||
pathType: {{ .pathType }}
|
||||
backend:
|
||||
service:
|
||||
name: {{ include "httpgate.fullname" $ }}-grpc
|
||||
port:
|
||||
number: {{ $.Values.httpPort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
{{- $tlsHost := . }}
|
||||
{{- if not (hasPrefix "*." $tlsHost) }}
|
||||
{{- $tlsHost = printf "*.%s" $tlsHost }}
|
||||
{{- end }}
|
||||
- {{ $tlsHost | quote }}
|
||||
{{- end }}
|
||||
{{- if .secretName }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user