mirror of
https://github.com/lukaszraczylo/kubernetes-images-sync-operator.git
synced 2026-06-05 22:53:39 +00:00
Initial commit for the operator
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file
|
||||
# Ignore build and test binaries.
|
||||
bin/
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# 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
|
||||
*~
|
||||
**/.DS_Store
|
||||
config/samples
|
||||
@@ -0,0 +1,47 @@
|
||||
run:
|
||||
timeout: 5m
|
||||
allow-parallel-runners: true
|
||||
|
||||
issues:
|
||||
# don't skip warning about doc comments
|
||||
# don't exclude the default set of lint
|
||||
exclude-use-default: false
|
||||
# restore some of the defaults
|
||||
# (fill in the rest as needed)
|
||||
exclude-rules:
|
||||
- path: "api/*"
|
||||
linters:
|
||||
- lll
|
||||
- path: "internal/*"
|
||||
linters:
|
||||
- dupl
|
||||
- lll
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- dupl
|
||||
- errcheck
|
||||
- exportloopref
|
||||
- ginkgolinter
|
||||
- goconst
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- goimports
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- lll
|
||||
- misspell
|
||||
- nakedret
|
||||
- prealloc
|
||||
- revive
|
||||
- staticcheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
|
||||
linters-settings:
|
||||
revive:
|
||||
rules:
|
||||
- name: comment-spacings
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
# Build the manager binary
|
||||
FROM golang:1.22 AS builder
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
||||
WORKDIR /workspace
|
||||
# Copy the Go Modules manifests
|
||||
COPY go.mod go.mod
|
||||
COPY go.sum go.sum
|
||||
# cache deps before building and copying source so that we don't need to re-download as much
|
||||
# and so that source changes don't invalidate our downloaded layer
|
||||
RUN go mod download
|
||||
|
||||
# Copy the go source
|
||||
COPY cmd/main.go cmd/main.go
|
||||
COPY api/ api/
|
||||
COPY internal/controller/ internal/controller/
|
||||
|
||||
# Build
|
||||
# the GOARCH has not a default value to allow the binary be built according to the host where the command
|
||||
# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
|
||||
# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,
|
||||
# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
|
||||
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go
|
||||
|
||||
# Use distroless as minimal base image to package the manager binary
|
||||
# Refer to https://github.com/GoogleContainerTools/distroless for more details
|
||||
FROM gcr.io/distroless/static:nonroot
|
||||
WORKDIR /
|
||||
COPY --from=builder /workspace/manager .
|
||||
USER 65532:65532
|
||||
|
||||
ENTRYPOINT ["/manager"]
|
||||
@@ -0,0 +1,221 @@
|
||||
# Image URL to use all building/pushing image targets
|
||||
IMG ?= ghcr.io/lukaszraczylo/kubernetes-images-sync-operator
|
||||
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
|
||||
ENVTEST_K8S_VERSION = 1.31.0
|
||||
|
||||
CURRENT_VERSION = $(shell semver-gen generate -l | awk '/^SEMVER/ {print $$NF}')
|
||||
ifeq ($(CURRENT_VERSION),)
|
||||
$(error Failed to extract version number)
|
||||
endif
|
||||
|
||||
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
|
||||
ifeq (,$(shell go env GOBIN))
|
||||
GOBIN=$(shell go env GOPATH)/bin
|
||||
else
|
||||
GOBIN=$(shell go env GOBIN)
|
||||
endif
|
||||
|
||||
# CONTAINER_TOOL defines the container tool to be used for building images.
|
||||
# Be aware that the target commands are only tested with Docker which is
|
||||
# scaffolded by default. However, you might want to replace it to use other
|
||||
# tools. (i.e. podman)
|
||||
CONTAINER_TOOL ?= docker
|
||||
|
||||
# Setting SHELL to bash allows bash commands to be executed by recipes.
|
||||
# Options are set to exit when a recipe line exits non-zero or a piped command fails.
|
||||
SHELL = /usr/bin/env bash -o pipefail
|
||||
.SHELLFLAGS = -ec
|
||||
|
||||
.PHONY: all
|
||||
all: build
|
||||
|
||||
##@ General
|
||||
|
||||
# The help target prints out all targets with their descriptions organized
|
||||
# beneath their categories. The categories are represented by '##@' and the
|
||||
# target descriptions by '##'. The awk command is responsible for reading the
|
||||
# entire set of makefiles included in this invocation, looking for lines of the
|
||||
# file as xyz: ## something, and then pretty-format the target and help. Then,
|
||||
# if there's a line with ##@ something, that gets pretty-printed as a category.
|
||||
# More info on the usage of ANSI control characters for terminal formatting:
|
||||
# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
|
||||
# More info on the awk command:
|
||||
# http://linuxcommand.org/lc3_adv_awk.php
|
||||
|
||||
.PHONY: help
|
||||
help: ## Display this help.
|
||||
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
||||
|
||||
##@ Development
|
||||
|
||||
.PHONY: manifests
|
||||
manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
|
||||
$(CONTROLLER_GEN) rbac:roleName=mr-raczylo-com 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) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out
|
||||
|
||||
# Utilize Kind or modify the e2e tests to load the image locally, enabling compatibility with other vendors.
|
||||
.PHONY: test-e2e # Run the e2e tests against a Kind k8s instance that is spun up.
|
||||
test-e2e:
|
||||
go test ./test/e2e/ -v -ginkgo.v
|
||||
|
||||
.PHONY: lint
|
||||
lint: golangci-lint ## Run golangci-lint linter
|
||||
$(GOLANGCI_LINT) run
|
||||
|
||||
.PHONY: lint-fix
|
||||
lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
|
||||
$(GOLANGCI_LINT) run --fix
|
||||
|
||||
##@ Build
|
||||
|
||||
.PHONY: build
|
||||
build: manifests generate fmt vet ## Build manager binary.
|
||||
go build -o bin/manager cmd/main.go
|
||||
|
||||
.PHONY: run
|
||||
run: manifests generate fmt vet ## Run a controller from your host.
|
||||
go run ./cmd/main.go
|
||||
|
||||
# If you wish to build the manager image targeting other platforms you can use the --platform flag.
|
||||
# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.
|
||||
# More info: https://docs.docker.com/develop/develop-images/build_enhancements/
|
||||
.PHONY: docker-build
|
||||
docker-build: ## Build docker image with the manager.
|
||||
$(CONTAINER_TOOL) build -t ${IMG}:${CURRENT_VERSION} .
|
||||
|
||||
.PHONY: docker-push
|
||||
docker-push: ## Push docker image with the manager.
|
||||
$(CONTAINER_TOOL) push ${IMG}:${CURRENT_VERSION}
|
||||
|
||||
# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple
|
||||
# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:
|
||||
# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/
|
||||
# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/
|
||||
# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=<myregistry/image:<tag>> then the export will fail)
|
||||
# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option.
|
||||
PLATFORMS ?= linux/arm64,linux/amd64
|
||||
.PHONY: docker-buildx
|
||||
docker-buildx: ## Build and push docker image for the manager for cross-platform support
|
||||
# copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile
|
||||
sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross
|
||||
- $(CONTAINER_TOOL) buildx create --name kubernetes-images-sync-operator-builder
|
||||
$(CONTAINER_TOOL) buildx use kubernetes-images-sync-operator-builder
|
||||
- $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG}:${CURRENT_VERSION} -f Dockerfile.cross .
|
||||
- $(CONTAINER_TOOL) buildx rm kubernetes-images-sync-operator-builder
|
||||
rm Dockerfile.cross
|
||||
|
||||
.PHONY: build-installer
|
||||
build-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment.
|
||||
mkdir -p dist
|
||||
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}:${CURRENT_VERSION}
|
||||
$(KUSTOMIZE) build config/default > dist/install.yaml
|
||||
|
||||
##@ Deployment
|
||||
|
||||
ifndef ignore-not-found
|
||||
ignore-not-found = false
|
||||
endif
|
||||
|
||||
.PHONY: install
|
||||
install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
|
||||
$(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: deploy
|
||||
deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
|
||||
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
|
||||
$(KUSTOMIZE) build config/default | $(KUBECTL) apply -f -
|
||||
|
||||
.PHONY: undeploy
|
||||
undeploy: kustomize ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
|
||||
$(KUSTOMIZE) build config/default | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f -
|
||||
|
||||
##@ Dependencies
|
||||
|
||||
## Location to install dependencies to
|
||||
LOCALBIN ?= $(shell pwd)/bin
|
||||
$(LOCALBIN):
|
||||
mkdir -p $(LOCALBIN)
|
||||
|
||||
## Tool Binaries
|
||||
KUBECTL ?= kubectl
|
||||
KUSTOMIZE ?= $(LOCALBIN)/kustomize
|
||||
CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
|
||||
ENVTEST ?= $(LOCALBIN)/setup-envtest
|
||||
GOLANGCI_LINT = $(LOCALBIN)/golangci-lint
|
||||
HELMIFY ?= helmify
|
||||
|
||||
## Tool Versions
|
||||
KUSTOMIZE_VERSION ?= v5.4.3
|
||||
CONTROLLER_TOOLS_VERSION ?= v0.16.1
|
||||
ENVTEST_VERSION ?= release-0.19
|
||||
GOLANGCI_LINT_VERSION ?= v1.59.1
|
||||
|
||||
.PHONY: print-version
|
||||
print-version:
|
||||
@echo "Current version: $(CURRENT_VERSION)"
|
||||
|
||||
.PHONY: kustomize
|
||||
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
|
||||
$(KUSTOMIZE): $(LOCALBIN)
|
||||
$(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION))
|
||||
|
||||
.PHONY: controller-gen
|
||||
controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.
|
||||
$(CONTROLLER_GEN): $(LOCALBIN)
|
||||
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION))
|
||||
|
||||
.PHONY: envtest
|
||||
envtest: $(ENVTEST) ## Download setup-envtest locally if necessary.
|
||||
$(ENVTEST): $(LOCALBIN)
|
||||
$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION))
|
||||
|
||||
.PHONY: golangci-lint
|
||||
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
|
||||
$(GOLANGCI_LINT): $(LOCALBIN)
|
||||
$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION))
|
||||
|
||||
.PHONY: helmify
|
||||
helmify: $(HELMIFY) ## Download helmify locally if necessary.
|
||||
$(HELMIFY): $(LOCALBIN)
|
||||
test -s $(LOCALBIN)/helmify || GOBIN=$(LOCALBIN) go install github.com/arttor/helmify/cmd/helmify@latest
|
||||
|
||||
.PHONY: helm
|
||||
helm: manifests kustomize helmify
|
||||
$(KUSTOMIZE) build config/default | helmify && \
|
||||
cp chart-defaults/Chart.yaml chart/Chart.yaml && \
|
||||
./update-version.sh $(CURRENT_VERSION) $(IMG)
|
||||
|
||||
# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
|
||||
# $1 - target path with name of binary
|
||||
# $2 - package url which can be installed
|
||||
# $3 - specific version of package
|
||||
define go-install-tool
|
||||
@[ -f "$(1)-$(3)" ] || { \
|
||||
set -e; \
|
||||
package=$(2)@$(3) ;\
|
||||
echo "Downloading $${package}" ;\
|
||||
rm -f $(1) || true ;\
|
||||
GOBIN=$(LOCALBIN) go install $${package} ;\
|
||||
mv $(1) $(1)-$(3) ;\
|
||||
} ;\
|
||||
ln -sf $(1)-$(3) $(1)
|
||||
endef
|
||||
@@ -0,0 +1,28 @@
|
||||
# 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: raczylo.com
|
||||
layout:
|
||||
- go.kubebuilder.io/v4
|
||||
multigroup: true
|
||||
projectName: kubernetes-images-sync-operator
|
||||
repo: raczylo.com/kubernetes-images-sync-operator
|
||||
resources:
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: true
|
||||
controller: true
|
||||
group: raczylo.com
|
||||
kind: ClusterImageExport
|
||||
path: raczylo.com/kubernetes-images-sync-operator/api/raczylo.com/v1
|
||||
version: v1
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: true
|
||||
controller: true
|
||||
group: raczylo.com
|
||||
kind: ClusterImage
|
||||
path: raczylo.com/kubernetes-images-sync-operator/api/raczylo.com/v1
|
||||
version: v1
|
||||
version: "3"
|
||||
@@ -0,0 +1,114 @@
|
||||
# kubernetes-images-sync-operator
|
||||
// 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>/kubernetes-images-sync-operator: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>/kubernetes-images-sync-operator: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>/kubernetes-images-sync-operator: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>/kubernetes-images-sync-operator/<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.
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
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 v1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// ClusterImageSpec defines the desired state of ClusterImage
|
||||
// +kubebuilder:printcolumn:name="Image",type="string",JSONPath=".spec.image"
|
||||
// +kubebuilder:printcolumn:name="Tag",type="string",JSONPath=".spec.tag"
|
||||
// +kubebuilder:printcolumn:name="SHA",type="string",JSONPath=".spec.sha"
|
||||
// +kubebuilder:printcolumn:name="Storage",type="string",JSONPath=".spec.storage"
|
||||
// +kubebuilder:printcolumn:name="Path",type="string",JSONPath=".spec.exportPath"
|
||||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
|
||||
type ClusterImageSpec struct {
|
||||
Image string `json:"image,omitempty"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Sha string `json:"sha,omitempty"`
|
||||
FullName string `json:"fullName,omitempty"` // Because I'm lazy and it's easier to pull that way
|
||||
Storage string `json:"storage,omitempty"`
|
||||
ExportName string `json:"exportName"`
|
||||
ExportPath string `json:"exportPath,omitempty"`
|
||||
}
|
||||
|
||||
// ClusterImageStatus defines the observed state of ClusterImage
|
||||
type ClusterImageStatus struct {
|
||||
Progress string `json:"progress,omitempty"`
|
||||
// default value is 0
|
||||
// +kubebuilder:default:=0
|
||||
RetryCount int `json:"retryCount,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// ClusterImage is the Schema for the clusterimages API
|
||||
// +kubebuilder:printcolumn:name="Ref",type="string",JSONPath=".spec.exportName"
|
||||
// +kubebuilder:printcolumn:name="Image",type="string",JSONPath=".spec.image"
|
||||
// +kubebuilder:printcolumn:name="Tag",type="string",JSONPath=".spec.tag"
|
||||
// +kubebuilder:printcolumn:name="SHA",type="string",JSONPath=".spec.sha"
|
||||
// +kubebuilder:printcolumn:name="Storage",type="string",JSONPath=".spec.storage"
|
||||
// +kubebuilder:printcolumn:name="Path",type="string",JSONPath=".spec.exportPath"
|
||||
// +kubebuilder:printcolumn:name="Progress",type="string",JSONPath=".status.progress"
|
||||
// +kubebuilder:printcolumn:name="Retries",type="integer",JSONPath=".status.retryCount"
|
||||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
|
||||
type ClusterImage struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec ClusterImageSpec `json:"spec,omitempty"`
|
||||
Status ClusterImageStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// ClusterImageList contains a list of ClusterImage
|
||||
type ClusterImageList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []ClusterImage `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&ClusterImage{}, &ClusterImageList{})
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
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 v1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type ClusterImageStorageS3 struct {
|
||||
// Bucket name
|
||||
Bucket string `json:"bucket"`
|
||||
Region string `json:"region"`
|
||||
// S3 bucket credentials
|
||||
AccessKey string `json:"accessKey,omitempty"`
|
||||
SecretKey string `json:"secretKey,omitempty"`
|
||||
UseRole bool `json:"useRole,omitempty"`
|
||||
// RoleARN is the ARN of the role to be used for the deployment
|
||||
RoleARN string `json:"roleARN,omitempty"`
|
||||
// Defines the endpoint for the S3 storage
|
||||
// If none specified - default AWS endpoint will be used
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
// Defines the secret name for credentials
|
||||
SecretName string `json:"secretName,omitempty"`
|
||||
}
|
||||
|
||||
// ClusterImageStorageSpec defines the desired state of ClusterImageStorage
|
||||
type ClusterImageStorageSpec struct {
|
||||
// +kubebuilder:validation:Enum=file;S3
|
||||
StorageTarget string `json:"target"`
|
||||
S3 ClusterImageStorageS3 `json:"s3,omitempty"`
|
||||
}
|
||||
|
||||
// ClusterImageExportSpec defines the desired state of ClusterImageExport
|
||||
// +kubebuilder:printcolumn:name="BasePath",type="string",JSONPath=".spec.basePath"
|
||||
// +kubebuilder:printcolumn:name="Storage",type="string",JSONPath=".spec.storage.target"
|
||||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
|
||||
type ClusterImageExportSpec struct {
|
||||
Name string `json:"name"`
|
||||
CreatedAt metav1.Time `json:"createdAt,omitempty"`
|
||||
// Exclude images which contain these strings
|
||||
Excludes []string `json:"excludes,omitempty"`
|
||||
// Include only images which contain these strings
|
||||
Includes []string `json:"includes,omitempty"`
|
||||
// Base path for the export - both file and S3
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:MaxLength=255
|
||||
BasePath string `json:"basePath"`
|
||||
Storage ClusterImageStorageSpec `json:"storage"`
|
||||
// +kubebuilder:validation.Minimum=1
|
||||
// +kubebuilder:validation.Maximum=100
|
||||
MaxConcurrentJobs int `json:"maxConcurrentJobs"`
|
||||
}
|
||||
|
||||
// ClusterImageExportStatus defines the observed state of ClusterImageExport
|
||||
type ClusterImageExportStatus struct {
|
||||
Progress string `json:"progress,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
|
||||
// ClusterImageExport is the Schema for the clusterimageexports API
|
||||
// +kubebuilder:printcolumn:name="BasePath",type="string",JSONPath=".spec.basePath"
|
||||
// +kubebuilder:printcolumn:name="Storage",type="string",JSONPath=".spec.storage.target"
|
||||
// +kubebuilder:printcolumn:name="Progress",type="string",JSONPath=".status.progress"
|
||||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
|
||||
type ClusterImageExport struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec ClusterImageExportSpec `json:"spec,omitempty"`
|
||||
Status ClusterImageExportStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// ClusterImageExportList contains a list of ClusterImageExport
|
||||
type ClusterImageExportList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []ClusterImageExport `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&ClusterImageExport{}, &ClusterImageExportList{})
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
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 v1 contains API Schema definitions for the raczylo.com v1 API group
|
||||
// +kubebuilder:object:generate=true
|
||||
// +groupName=raczylo.com
|
||||
package v1
|
||||
|
||||
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: "raczylo.com", Version: "v1"}
|
||||
|
||||
// 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
|
||||
)
|
||||
@@ -0,0 +1,246 @@
|
||||
//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 v1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClusterImage) DeepCopyInto(out *ClusterImage) {
|
||||
*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 ClusterImage.
|
||||
func (in *ClusterImage) DeepCopy() *ClusterImage {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ClusterImage)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ClusterImage) 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 *ClusterImageExport) DeepCopyInto(out *ClusterImageExport) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
out.Status = in.Status
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterImageExport.
|
||||
func (in *ClusterImageExport) DeepCopy() *ClusterImageExport {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ClusterImageExport)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ClusterImageExport) 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 *ClusterImageExportList) DeepCopyInto(out *ClusterImageExportList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]ClusterImageExport, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterImageExportList.
|
||||
func (in *ClusterImageExportList) DeepCopy() *ClusterImageExportList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ClusterImageExportList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ClusterImageExportList) 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 *ClusterImageExportSpec) DeepCopyInto(out *ClusterImageExportSpec) {
|
||||
*out = *in
|
||||
in.CreatedAt.DeepCopyInto(&out.CreatedAt)
|
||||
if in.Excludes != nil {
|
||||
in, out := &in.Excludes, &out.Excludes
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Includes != nil {
|
||||
in, out := &in.Includes, &out.Includes
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
out.Storage = in.Storage
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterImageExportSpec.
|
||||
func (in *ClusterImageExportSpec) DeepCopy() *ClusterImageExportSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ClusterImageExportSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClusterImageExportStatus) DeepCopyInto(out *ClusterImageExportStatus) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterImageExportStatus.
|
||||
func (in *ClusterImageExportStatus) DeepCopy() *ClusterImageExportStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ClusterImageExportStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClusterImageList) DeepCopyInto(out *ClusterImageList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]ClusterImage, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterImageList.
|
||||
func (in *ClusterImageList) DeepCopy() *ClusterImageList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ClusterImageList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ClusterImageList) 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 *ClusterImageSpec) DeepCopyInto(out *ClusterImageSpec) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterImageSpec.
|
||||
func (in *ClusterImageSpec) DeepCopy() *ClusterImageSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ClusterImageSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClusterImageStatus) DeepCopyInto(out *ClusterImageStatus) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterImageStatus.
|
||||
func (in *ClusterImageStatus) DeepCopy() *ClusterImageStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ClusterImageStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClusterImageStorageS3) DeepCopyInto(out *ClusterImageStorageS3) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterImageStorageS3.
|
||||
func (in *ClusterImageStorageS3) DeepCopy() *ClusterImageStorageS3 {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ClusterImageStorageS3)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClusterImageStorageSpec) DeepCopyInto(out *ClusterImageStorageSpec) {
|
||||
*out = *in
|
||||
out.S3 = in.S3
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterImageStorageSpec.
|
||||
func (in *ClusterImageStorageSpec) DeepCopy() *ClusterImageStorageSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ClusterImageStorageSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
apiVersion: v2
|
||||
name: kube-images-sync
|
||||
description: |
|
||||
A Helm chart for Kubernetes Images Sync Operator.
|
||||
Kubernetes Images Sync Operator is responsible for backing up and restoring images from a Kubernetes cluster.
|
||||
It's ultimate goal is to provide synchonization of images between multiple environments, quite often air-gapped.
|
||||
It compiles the list of images currently present in the cluster and uploads them to the specified storage.
|
||||
Whenever new CRD is created - it will try to figure out which images were already uploaded and which are new and
|
||||
upload only the new ones to avoid repetition.
|
||||
|
||||
type: application
|
||||
|
||||
version: 0.0.0
|
||||
|
||||
appVersion: "0.0.0"
|
||||
|
||||
home: https://github.com/lukaszraczylo/kubernetes-images-sync-operator
|
||||
|
||||
maintainers:
|
||||
- name: lukaszraczylo
|
||||
email: github-enquiries@raczylo.com
|
||||
@@ -0,0 +1,23 @@
|
||||
# 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/
|
||||
@@ -0,0 +1,21 @@
|
||||
apiVersion: v2
|
||||
name: kube-images-sync
|
||||
description: |
|
||||
A Helm chart for Kubernetes Images Sync Operator.
|
||||
Kubernetes Images Sync Operator is responsible for backing up and restoring images from a Kubernetes cluster.
|
||||
It's ultimate goal is to provide synchonization of images between multiple environments, quite often air-gapped.
|
||||
It compiles the list of images currently present in the cluster and uploads them to the specified storage.
|
||||
Whenever new CRD is created - it will try to figure out which images were already uploaded and which are new and
|
||||
upload only the new ones to avoid repetition.
|
||||
|
||||
type: application
|
||||
|
||||
version: 0.0.26
|
||||
|
||||
appVersion: "0.0.26"
|
||||
|
||||
home: https://github.com/lukaszraczylo/kubernetes-images-sync-operator
|
||||
|
||||
maintainers:
|
||||
- name: lukaszraczylo
|
||||
email: github-enquiries@raczylo.com
|
||||
@@ -0,0 +1,62 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "chart.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 "chart.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 "chart.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "chart.labels" -}}
|
||||
helm.sh/chart: {{ include "chart.chart" . }}
|
||||
{{ include "chart.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "chart.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "chart.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "chart.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "chart.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,108 @@
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: clusterimages.raczylo.com
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.16.1
|
||||
labels:
|
||||
{{- include "chart.labels" . | nindent 4 }}
|
||||
spec:
|
||||
group: raczylo.com
|
||||
names:
|
||||
kind: ClusterImage
|
||||
listKind: ClusterImageList
|
||||
plural: clusterimages
|
||||
singular: clusterimage
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .spec.exportName
|
||||
name: Ref
|
||||
type: string
|
||||
- jsonPath: .spec.image
|
||||
name: Image
|
||||
type: string
|
||||
- jsonPath: .spec.tag
|
||||
name: Tag
|
||||
type: string
|
||||
- jsonPath: .spec.sha
|
||||
name: SHA
|
||||
type: string
|
||||
- jsonPath: .spec.storage
|
||||
name: Storage
|
||||
type: string
|
||||
- jsonPath: .spec.exportPath
|
||||
name: Path
|
||||
type: string
|
||||
- jsonPath: .status.progress
|
||||
name: Progress
|
||||
type: string
|
||||
- jsonPath: .status.retryCount
|
||||
name: Retries
|
||||
type: integer
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: ClusterImage is the Schema for the clusterimages 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: ClusterImageSpec defines the desired state of ClusterImage
|
||||
properties:
|
||||
exportName:
|
||||
type: string
|
||||
exportPath:
|
||||
type: string
|
||||
fullName:
|
||||
type: string
|
||||
image:
|
||||
type: string
|
||||
sha:
|
||||
type: string
|
||||
storage:
|
||||
type: string
|
||||
tag:
|
||||
type: string
|
||||
required:
|
||||
- exportName
|
||||
type: object
|
||||
status:
|
||||
description: ClusterImageStatus defines the observed state of ClusterImage
|
||||
properties:
|
||||
progress:
|
||||
type: string
|
||||
retryCount:
|
||||
default: 0
|
||||
description: default value is 0
|
||||
type: integer
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -0,0 +1,141 @@
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: clusterimageexports.raczylo.com
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.16.1
|
||||
labels:
|
||||
{{- include "chart.labels" . | nindent 4 }}
|
||||
spec:
|
||||
group: raczylo.com
|
||||
names:
|
||||
kind: ClusterImageExport
|
||||
listKind: ClusterImageExportList
|
||||
plural: clusterimageexports
|
||||
singular: clusterimageexport
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .spec.basePath
|
||||
name: BasePath
|
||||
type: string
|
||||
- jsonPath: .spec.storage.target
|
||||
name: Storage
|
||||
type: string
|
||||
- jsonPath: .status.progress
|
||||
name: Progress
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: ClusterImageExport is the Schema for the clusterimageexports 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: ClusterImageExportSpec defines the desired state of ClusterImageExport
|
||||
properties:
|
||||
basePath:
|
||||
description: Base path for the export - both file and S3
|
||||
maxLength: 255
|
||||
minLength: 1
|
||||
type: string
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
excludes:
|
||||
description: Exclude images which contain these strings
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
includes:
|
||||
description: Include only images which contain these strings
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
maxConcurrentJobs:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
storage:
|
||||
description: ClusterImageStorageSpec defines the desired state of ClusterImageStorage
|
||||
properties:
|
||||
s3:
|
||||
properties:
|
||||
accessKey:
|
||||
description: S3 bucket credentials
|
||||
type: string
|
||||
bucket:
|
||||
description: Bucket name
|
||||
type: string
|
||||
endpoint:
|
||||
description: |-
|
||||
Defines the endpoint for the S3 storage
|
||||
If none specified - default AWS endpoint will be used
|
||||
type: string
|
||||
region:
|
||||
type: string
|
||||
roleARN:
|
||||
description: RoleARN is the ARN of the role to be used for the
|
||||
deployment
|
||||
type: string
|
||||
secretKey:
|
||||
type: string
|
||||
secretName:
|
||||
description: Defines the secret name for credentials
|
||||
type: string
|
||||
useRole:
|
||||
type: boolean
|
||||
required:
|
||||
- bucket
|
||||
- region
|
||||
type: object
|
||||
target:
|
||||
enum:
|
||||
- file
|
||||
- S3
|
||||
type: string
|
||||
required:
|
||||
- target
|
||||
type: object
|
||||
required:
|
||||
- basePath
|
||||
- maxConcurrentJobs
|
||||
- name
|
||||
- storage
|
||||
type: object
|
||||
status:
|
||||
description: ClusterImageExportStatus defines the observed state of ClusterImageExport
|
||||
properties:
|
||||
progress:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "chart.fullname" . }}-cm-raczylo-com-metrics-service
|
||||
labels:
|
||||
control-plane: cm-raczylo-com
|
||||
{{- include "chart.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.cmRaczyloComMetricsService.type }}
|
||||
selector:
|
||||
control-plane: cm-raczylo-com
|
||||
{{- include "chart.selectorLabels" . | nindent 4 }}
|
||||
ports:
|
||||
{{- .Values.cmRaczyloComMetricsService.ports | toYaml | nindent 2 }}
|
||||
@@ -0,0 +1,50 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "chart.fullname" . }}-cm-raczylo-com
|
||||
labels:
|
||||
control-plane: cm-raczylo-com
|
||||
{{- include "chart.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.cmRaczyloCom.replicas }}
|
||||
selector:
|
||||
matchLabels:
|
||||
control-plane: cm-raczylo-com
|
||||
{{- include "chart.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
control-plane: cm-raczylo-com
|
||||
{{- include "chart.selectorLabels" . | nindent 8 }}
|
||||
annotations:
|
||||
kubectl.kubernetes.io/default-container: manager
|
||||
spec:
|
||||
containers:
|
||||
- args: {{- toYaml .Values.cmRaczyloCom.manager.args | nindent 8 }}
|
||||
command:
|
||||
- /manager
|
||||
env:
|
||||
- name: KUBERNETES_CLUSTER_DOMAIN
|
||||
value: {{ quote .Values.kubernetesClusterDomain }}
|
||||
image: {{ .Values.cmRaczyloCom.manager.image.repository }}:{{ .Values.cmRaczyloCom.manager.image.tag
|
||||
| default .Chart.AppVersion }}
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8081
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 20
|
||||
name: manager
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /readyz
|
||||
port: 8081
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
resources: {{- toYaml .Values.cmRaczyloCom.manager.resources | nindent 10 }}
|
||||
securityContext: {{- toYaml .Values.cmRaczyloCom.manager.containerSecurityContext
|
||||
| nindent 10 }}
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
serviceAccountName: {{ include "chart.fullname" . }}-cm-raczylo-com
|
||||
terminationGracePeriodSeconds: 10
|
||||
@@ -0,0 +1,19 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: {{ include "chart.fullname" . }}-metrics-auth-raczylo
|
||||
labels:
|
||||
{{- include "chart.labels" . | nindent 4 }}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- authentication.k8s.io
|
||||
resources:
|
||||
- tokenreviews
|
||||
verbs:
|
||||
- create
|
||||
- apiGroups:
|
||||
- authorization.k8s.io
|
||||
resources:
|
||||
- subjectaccessreviews
|
||||
verbs:
|
||||
- create
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: {{ include "chart.fullname" . }}-metrics-auth-raczylobinding
|
||||
labels:
|
||||
{{- include "chart.labels" . | nindent 4 }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: '{{ include "chart.fullname" . }}-metrics-auth-raczylo'
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: '{{ include "chart.fullname" . }}-cm-raczylo-com'
|
||||
namespace: '{{ .Release.Namespace }}'
|
||||
@@ -0,0 +1,11 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: {{ include "chart.fullname" . }}-metrics-raczylo
|
||||
labels:
|
||||
{{- include "chart.labels" . | nindent 4 }}
|
||||
rules:
|
||||
- nonResourceURLs:
|
||||
- /metrics
|
||||
verbs:
|
||||
- get
|
||||
@@ -0,0 +1,63 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: {{ include "chart.fullname" . }}-mr-raczylo-com
|
||||
labels:
|
||||
{{- include "chart.labels" . | nindent 4 }}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- daemonsets
|
||||
- deployments
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- batch
|
||||
resources:
|
||||
- cronjobs
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- batch
|
||||
resources:
|
||||
- jobs
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- clusterimageexports
|
||||
- clusterimages
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- clusterimageexports/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- clusterimageexports/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: {{ include "chart.fullname" . }}-mr-raczylo-combinding
|
||||
labels:
|
||||
{{- include "chart.labels" . | nindent 4 }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: '{{ include "chart.fullname" . }}-mr-raczylo-com'
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: '{{ include "chart.fullname" . }}-cm-raczylo-com'
|
||||
namespace: '{{ .Release.Namespace }}'
|
||||
@@ -0,0 +1,38 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: {{ include "chart.fullname" . }}-raczylo-com-leader
|
||||
labels:
|
||||
{{- include "chart.labels" . | nindent 4 }}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- create
|
||||
- update
|
||||
- patch
|
||||
- delete
|
||||
- apiGroups:
|
||||
- coordination.k8s.io
|
||||
resources:
|
||||
- leases
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- create
|
||||
- update
|
||||
- patch
|
||||
- delete
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- events
|
||||
verbs:
|
||||
- create
|
||||
- patch
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: {{ include "chart.fullname" . }}-raczylo-com-leaderbinding
|
||||
labels:
|
||||
{{- include "chart.labels" . | nindent 4 }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: '{{ include "chart.fullname" . }}-raczylo-com-leader'
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: '{{ include "chart.fullname" . }}-cm-raczylo-com'
|
||||
namespace: '{{ .Release.Namespace }}'
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: {{ include "chart.fullname" . }}-raczylo.com-clusterimage-editor-role
|
||||
labels:
|
||||
{{- include "chart.labels" . | nindent 4 }}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- clusterimages
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- clusterimages/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -0,0 +1,21 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: {{ include "chart.fullname" . }}-raczylo.com-clusterimage-viewer-role
|
||||
labels:
|
||||
{{- include "chart.labels" . | nindent 4 }}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- clusterimages
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- clusterimages/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: {{ include "chart.fullname" . }}-raczylo.com-clusterimageexport-editor-role
|
||||
labels:
|
||||
{{- include "chart.labels" . | nindent 4 }}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- clusterimageexports
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- clusterimageexports/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -0,0 +1,21 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: {{ include "chart.fullname" . }}-raczylo.com-clusterimageexport-viewer-role
|
||||
labels:
|
||||
{{- include "chart.labels" . | nindent 4 }}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- clusterimageexports
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- clusterimageexports/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "chart.fullname" . }}-cm-raczylo-com
|
||||
labels:
|
||||
{{- include "chart.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
{{- toYaml .Values.cmRaczyloCom.serviceAccount.annotations | nindent 4 }}
|
||||
@@ -0,0 +1,32 @@
|
||||
cmRaczyloCom:
|
||||
manager:
|
||||
args:
|
||||
- --metrics-bind-address=:8443
|
||||
- --leader-elect
|
||||
- --health-probe-bind-address=:8081
|
||||
containerSecurityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
image:
|
||||
repository: controller
|
||||
tag: latest
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 128Mi
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 64Mi
|
||||
replicas: 1
|
||||
serviceAccount:
|
||||
annotations: {}
|
||||
cmRaczyloComMetricsService:
|
||||
ports:
|
||||
- name: https
|
||||
port: 8443
|
||||
protocol: TCP
|
||||
targetPort: 8443
|
||||
type: ClusterIP
|
||||
kubernetesClusterDomain: cluster.local
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
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 (
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
|
||||
// to ensure that exec-entrypoint and run can make use of them.
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/healthz"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
|
||||
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||
|
||||
raczylocomv1 "raczylo.com/kubernetes-images-sync-operator/api/raczylo.com/v1"
|
||||
raczylocomcontroller "raczylo.com/kubernetes-images-sync-operator/internal/controller/raczylo.com"
|
||||
"raczylo.com/kubernetes-images-sync-operator/shared"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
var (
|
||||
scheme = runtime.NewScheme()
|
||||
setupLog = ctrl.Log.WithName("setup")
|
||||
)
|
||||
|
||||
func init() {
|
||||
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
|
||||
|
||||
utilruntime.Must(raczylocomv1.AddToScheme(scheme))
|
||||
// +kubebuilder:scaffold:scheme
|
||||
}
|
||||
|
||||
func main() {
|
||||
var metricsAddr string
|
||||
var enableLeaderElection bool
|
||||
var probeAddr string
|
||||
var secureMetrics bool
|
||||
var enableHTTP2 bool
|
||||
var tlsOpts []func(*tls.Config)
|
||||
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
|
||||
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
|
||||
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
|
||||
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
|
||||
"Enable leader election for controller manager. "+
|
||||
"Enabling this will ensure there is only one active controller manager.")
|
||||
flag.BoolVar(&secureMetrics, "metrics-secure", true,
|
||||
"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.")
|
||||
flag.BoolVar(&enableHTTP2, "enable-http2", false,
|
||||
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
|
||||
opts := zap.Options{
|
||||
Development: true,
|
||||
}
|
||||
opts.BindFlags(flag.CommandLine)
|
||||
flag.Parse()
|
||||
|
||||
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
|
||||
|
||||
// if the enable-http2 flag is false (the default), http/2 should be disabled
|
||||
// due to its vulnerabilities. More specifically, disabling http/2 will
|
||||
// prevent from being vulnerable to the HTTP/2 Stream Cancellation and
|
||||
// Rapid Reset CVEs. For more information see:
|
||||
// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3
|
||||
// - https://github.com/advisories/GHSA-4374-p667-p6c8
|
||||
disableHTTP2 := func(c *tls.Config) {
|
||||
setupLog.Info("disabling http/2")
|
||||
c.NextProtos = []string{"http/1.1"}
|
||||
}
|
||||
|
||||
if !enableHTTP2 {
|
||||
tlsOpts = append(tlsOpts, disableHTTP2)
|
||||
}
|
||||
|
||||
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.19.0/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.19.0/pkg/metrics/filters#WithAuthenticationAndAuthorization
|
||||
metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization
|
||||
}
|
||||
|
||||
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
|
||||
Scheme: scheme,
|
||||
Metrics: metricsServerOptions,
|
||||
WebhookServer: webhookServer,
|
||||
HealthProbeBindAddress: probeAddr,
|
||||
LeaderElection: enableLeaderElection,
|
||||
LeaderElectionID: "97ba1beb.raczylo.com",
|
||||
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
|
||||
// when the Manager ends. This requires the binary to immediately end when the
|
||||
// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
|
||||
// speeds up voluntary leader transitions as the new leader don't have to wait
|
||||
// LeaseDuration time first.
|
||||
//
|
||||
// In the default scaffold provided, the program ends immediately after
|
||||
// the manager stops, so would be fine to enable this option. However,
|
||||
// if you are doing or is intended to do any operation such as perform cleanups
|
||||
// after the manager stops then its usage might be unsafe.
|
||||
// LeaderElectionReleaseOnCancel: true,
|
||||
})
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to start manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := shared.SetupIndexers(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to setup indexers")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err = (&raczylocomcontroller.ClusterImageExportReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "ClusterImageExport")
|
||||
os.Exit(1)
|
||||
}
|
||||
if err = (&raczylocomcontroller.ClusterImageReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "ClusterImage")
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.16.1
|
||||
name: clusterimageexports.raczylo.com
|
||||
spec:
|
||||
group: raczylo.com
|
||||
names:
|
||||
kind: ClusterImageExport
|
||||
listKind: ClusterImageExportList
|
||||
plural: clusterimageexports
|
||||
singular: clusterimageexport
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .spec.basePath
|
||||
name: BasePath
|
||||
type: string
|
||||
- jsonPath: .spec.storage.target
|
||||
name: Storage
|
||||
type: string
|
||||
- jsonPath: .status.progress
|
||||
name: Progress
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: ClusterImageExport is the Schema for the clusterimageexports
|
||||
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: ClusterImageExportSpec defines the desired state of ClusterImageExport
|
||||
properties:
|
||||
basePath:
|
||||
description: Base path for the export - both file and S3
|
||||
maxLength: 255
|
||||
minLength: 1
|
||||
type: string
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
excludes:
|
||||
description: Exclude images which contain these strings
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
includes:
|
||||
description: Include only images which contain these strings
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
maxConcurrentJobs:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
storage:
|
||||
description: ClusterImageStorageSpec defines the desired state of
|
||||
ClusterImageStorage
|
||||
properties:
|
||||
s3:
|
||||
properties:
|
||||
accessKey:
|
||||
description: S3 bucket credentials
|
||||
type: string
|
||||
bucket:
|
||||
description: Bucket name
|
||||
type: string
|
||||
endpoint:
|
||||
description: |-
|
||||
Defines the endpoint for the S3 storage
|
||||
If none specified - default AWS endpoint will be used
|
||||
type: string
|
||||
region:
|
||||
type: string
|
||||
roleARN:
|
||||
description: RoleARN is the ARN of the role to be used for
|
||||
the deployment
|
||||
type: string
|
||||
secretKey:
|
||||
type: string
|
||||
secretName:
|
||||
description: Defines the secret name for credentials
|
||||
type: string
|
||||
useRole:
|
||||
type: boolean
|
||||
required:
|
||||
- bucket
|
||||
- region
|
||||
type: object
|
||||
target:
|
||||
enum:
|
||||
- file
|
||||
- S3
|
||||
type: string
|
||||
required:
|
||||
- target
|
||||
type: object
|
||||
required:
|
||||
- basePath
|
||||
- maxConcurrentJobs
|
||||
- name
|
||||
- storage
|
||||
type: object
|
||||
status:
|
||||
description: ClusterImageExportStatus defines the observed state of ClusterImageExport
|
||||
properties:
|
||||
progress:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.16.1
|
||||
name: clusterimages.raczylo.com
|
||||
spec:
|
||||
group: raczylo.com
|
||||
names:
|
||||
kind: ClusterImage
|
||||
listKind: ClusterImageList
|
||||
plural: clusterimages
|
||||
singular: clusterimage
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .spec.exportName
|
||||
name: Ref
|
||||
type: string
|
||||
- jsonPath: .spec.image
|
||||
name: Image
|
||||
type: string
|
||||
- jsonPath: .spec.tag
|
||||
name: Tag
|
||||
type: string
|
||||
- jsonPath: .spec.sha
|
||||
name: SHA
|
||||
type: string
|
||||
- jsonPath: .spec.storage
|
||||
name: Storage
|
||||
type: string
|
||||
- jsonPath: .spec.exportPath
|
||||
name: Path
|
||||
type: string
|
||||
- jsonPath: .status.progress
|
||||
name: Progress
|
||||
type: string
|
||||
- jsonPath: .status.retryCount
|
||||
name: Retries
|
||||
type: integer
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: ClusterImage is the Schema for the clusterimages 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: ClusterImageSpec defines the desired state of ClusterImage
|
||||
properties:
|
||||
exportName:
|
||||
type: string
|
||||
exportPath:
|
||||
type: string
|
||||
fullName:
|
||||
type: string
|
||||
image:
|
||||
type: string
|
||||
sha:
|
||||
type: string
|
||||
storage:
|
||||
type: string
|
||||
tag:
|
||||
type: string
|
||||
required:
|
||||
- exportName
|
||||
type: object
|
||||
status:
|
||||
description: ClusterImageStatus defines the observed state of ClusterImage
|
||||
properties:
|
||||
progress:
|
||||
type: string
|
||||
retryCount:
|
||||
default: 0
|
||||
description: default value is 0
|
||||
type: integer
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
@@ -0,0 +1,24 @@
|
||||
# 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/raczylo.com_clusterimageexports.yaml
|
||||
- bases/raczylo.com_clusterimages.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_raczylo.com_clusterimageexports.yaml
|
||||
#- path: patches/cainjection_in_raczylo.com_clusterimages.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
|
||||
@@ -0,0 +1,19 @@
|
||||
# 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
|
||||
@@ -0,0 +1,151 @@
|
||||
# Adds namespace to all resources.
|
||||
namespace: kubernetes-images-sync-operator-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: kubernetes-images-sync-operator-
|
||||
|
||||
# Labels to add to all resources and selectors.
|
||||
#labels:
|
||||
#- includeSelectors: true
|
||||
# pairs:
|
||||
# someName: someValue
|
||||
|
||||
resources:
|
||||
- ../crd
|
||||
- ../rbac
|
||||
- ../manager
|
||||
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
|
||||
# crd/kustomization.yaml
|
||||
#- ../webhook
|
||||
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.
|
||||
#- ../certmanager
|
||||
# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.
|
||||
#- ../prometheus
|
||||
# [METRICS] Expose the controller manager metrics service.
|
||||
- metrics_service.yaml
|
||||
# [NETWORK POLICY] Protect the /metrics endpoint and Webhook Server with NetworkPolicy.
|
||||
# Only Pod(s) running a namespace labeled with 'metrics: enabled' will be able to gather the metrics.
|
||||
# Only CR(s) which requires webhooks and are applied on namespaces labeled with 'webhooks: enabled' will
|
||||
# be able to communicate with the Webhook Server.
|
||||
#- ../network-policy
|
||||
|
||||
# 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
|
||||
@@ -0,0 +1,4 @@
|
||||
# This patch adds the args to allow exposing the metrics endpoint using HTTPS
|
||||
- op: add
|
||||
path: /spec/template/spec/containers/0/args/0
|
||||
value: --metrics-bind-address=:8443
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
control-plane: cm-raczylo-com
|
||||
app.kubernetes.io/name: kubernetes-images-sync-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: cm-raczylo-com-metrics-service
|
||||
namespace: system
|
||||
spec:
|
||||
ports:
|
||||
- name: https
|
||||
port: 8443
|
||||
protocol: TCP
|
||||
targetPort: 8443
|
||||
selector:
|
||||
control-plane: cm-raczylo-com
|
||||
@@ -0,0 +1,2 @@
|
||||
resources:
|
||||
- manager.yaml
|
||||
@@ -0,0 +1,95 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
labels:
|
||||
control-plane: cm-raczylo-com
|
||||
app.kubernetes.io/name: kubernetes-images-sync-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: system
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: cm-raczylo-com
|
||||
namespace: system
|
||||
labels:
|
||||
control-plane: cm-raczylo-com
|
||||
app.kubernetes.io/name: kubernetes-images-sync-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
control-plane: cm-raczylo-com
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
kubectl.kubernetes.io/default-container: manager
|
||||
labels:
|
||||
control-plane: cm-raczylo-com
|
||||
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
|
||||
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: 500m
|
||||
memory: 128Mi
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 64Mi
|
||||
serviceAccountName: cm-raczylo-com
|
||||
terminationGracePeriodSeconds: 10
|
||||
@@ -0,0 +1,26 @@
|
||||
# This NetworkPolicy allows ingress traffic
|
||||
# with Pods running on namespaces labeled with 'metrics: enabled'. Only Pods on those
|
||||
# namespaces are able to gathering data from the metrics endpoint.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: kubernetes-images-sync-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: allow-metrics-traffic
|
||||
namespace: system
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
control-plane: cm-raczylo-com
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
# This allows ingress traffic from any namespace with the label metrics: enabled
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
metrics: enabled # Only from namespaces with this label
|
||||
ports:
|
||||
- port: 8443
|
||||
protocol: TCP
|
||||
@@ -0,0 +1,2 @@
|
||||
resources:
|
||||
- allow-metrics-traffic.yaml
|
||||
@@ -0,0 +1,2 @@
|
||||
resources:
|
||||
- monitor.yaml
|
||||
@@ -0,0 +1,30 @@
|
||||
# Prometheus Monitor Service (Metrics)
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
labels:
|
||||
control-plane: cm-raczylo-com
|
||||
app.kubernetes.io/name: kubernetes-images-sync-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: cm-raczylo-com-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: cm-raczylo-com
|
||||
@@ -0,0 +1,29 @@
|
||||
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.
|
||||
- raczylo.com_clusterimage_editor_role.yaml
|
||||
- raczylo.com_clusterimage_viewer_role.yaml
|
||||
- raczylo.com_clusterimageexport_editor_role.yaml
|
||||
- raczylo.com_clusterimageexport_viewer_role.yaml
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# permissions to do leader election.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: kubernetes-images-sync-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: raczylo-com-leader
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- create
|
||||
- update
|
||||
- patch
|
||||
- delete
|
||||
- apiGroups:
|
||||
- coordination.k8s.io
|
||||
resources:
|
||||
- leases
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- create
|
||||
- update
|
||||
- patch
|
||||
- delete
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- events
|
||||
verbs:
|
||||
- create
|
||||
- patch
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: kubernetes-images-sync-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: raczylo-com-leaderbinding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: raczylo-com-leader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: cm-raczylo-com
|
||||
namespace: system
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: metrics-auth-raczylo
|
||||
rules:
|
||||
- apiGroups:
|
||||
- authentication.k8s.io
|
||||
resources:
|
||||
- tokenreviews
|
||||
verbs:
|
||||
- create
|
||||
- apiGroups:
|
||||
- authorization.k8s.io
|
||||
resources:
|
||||
- subjectaccessreviews
|
||||
verbs:
|
||||
- create
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: metrics-auth-raczylobinding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: metrics-auth-raczylo
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: cm-raczylo-com
|
||||
namespace: system
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: metrics-raczylo
|
||||
rules:
|
||||
- nonResourceURLs:
|
||||
- "/metrics"
|
||||
verbs:
|
||||
- get
|
||||
@@ -0,0 +1,27 @@
|
||||
# permissions for end users to edit clusterimages.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: kubernetes-images-sync-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: raczylo.com-clusterimage-editor-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- clusterimages
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- clusterimages/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -0,0 +1,23 @@
|
||||
# permissions for end users to view clusterimages.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: kubernetes-images-sync-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: raczylo.com-clusterimage-viewer-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- clusterimages
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- clusterimages/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -0,0 +1,27 @@
|
||||
# permissions for end users to edit clusterimageexports.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: kubernetes-images-sync-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: raczylo.com-clusterimageexport-editor-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- clusterimageexports
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- clusterimageexports/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -0,0 +1,23 @@
|
||||
# permissions for end users to view clusterimageexports.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: kubernetes-images-sync-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: raczylo.com-clusterimageexport-viewer-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- clusterimageexports
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- clusterimageexports/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: mr-raczylo-com
|
||||
rules:
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- daemonsets
|
||||
- deployments
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- batch
|
||||
resources:
|
||||
- cronjobs
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- batch
|
||||
resources:
|
||||
- jobs
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- clusterimageexports
|
||||
- clusterimages
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- clusterimageexports/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- raczylo.com
|
||||
resources:
|
||||
- clusterimageexports/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: kubernetes-images-sync-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: mr-raczylo-combinding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: mr-raczylo-com
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: cm-raczylo-com
|
||||
namespace: system
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: kubernetes-images-sync-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: cm-raczylo-com
|
||||
namespace: system
|
||||
@@ -0,0 +1,36 @@
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ARG TARGETPLATFORM
|
||||
ARG TARGETARCH
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
gnupg2 \
|
||||
python3-pip \
|
||||
sudo \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN echo "deb [arch=${TARGETARCH}] https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/unstable/xUbuntu_22.04/ /" | tee /etc/apt/sources.list.d/devel:kubic:libcontainers:unstable.list \
|
||||
&& curl -fsSL "https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/unstable/xUbuntu_22.04/Release.key" | apt-key add -
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
uidmap \
|
||||
fuse-overlayfs \
|
||||
podman \
|
||||
netavark \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN adduser --disabled-password --gecos "" --uid 1001 runner \
|
||||
&& groupadd docker --gid 123 \
|
||||
&& usermod -aG sudo,docker runner \
|
||||
&& echo "%sudo ALL=(ALL:ALL) NOPASSWD:ALL" > /etc/sudoers \
|
||||
&& echo "Defaults env_keep += \"DEBIAN_FRONTEND\"" >> /etc/sudoers
|
||||
|
||||
WORKDIR /home/runner
|
||||
|
||||
COPY storage.conf containers.conf registries.conf /home/runner/.config/containers/
|
||||
COPY requirements.txt export.py cleanup.py ./
|
||||
USER runner
|
||||
RUN sudo chown -R runner:runner /home/runner/.config \
|
||||
&& python3 -m pip install --no-cache-dir --only-binary=:all: -r requirements.txt
|
||||
Executable
+118
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import boto3
|
||||
import argparse
|
||||
from botocore.exceptions import ClientError
|
||||
|
||||
def get_s3_client(use_role=False, role_name=None, aws_access_key_id=None, aws_secret_access_key=None, endpoint_url=None, region=None):
|
||||
"""
|
||||
Create and return an S3 client based on the provided authentication method, endpoint, and region.
|
||||
"""
|
||||
client_kwargs = {}
|
||||
|
||||
if endpoint_url:
|
||||
client_kwargs['endpoint_url'] = endpoint_url
|
||||
elif region:
|
||||
client_kwargs['region_name'] = region
|
||||
|
||||
if use_role:
|
||||
if role_name:
|
||||
# Assume the specified role
|
||||
sts_client = boto3.client('sts')
|
||||
assumed_role_object = sts_client.assume_role(
|
||||
RoleArn=f"arn:aws:iam::{boto3.client('sts').get_caller_identity()['Account']}:role/{role_name}",
|
||||
RoleSessionName="AssumeRoleSession"
|
||||
)
|
||||
credentials = assumed_role_object['Credentials']
|
||||
client_kwargs['aws_access_key_id'] = credentials['AccessKeyId']
|
||||
client_kwargs['aws_secret_access_key'] = credentials['SecretAccessKey']
|
||||
client_kwargs['aws_session_token'] = credentials['SessionToken']
|
||||
return boto3.client('s3', **client_kwargs)
|
||||
elif aws_access_key_id and aws_secret_access_key:
|
||||
client_kwargs['aws_access_key_id'] = aws_access_key_id
|
||||
client_kwargs['aws_secret_access_key'] = aws_secret_access_key
|
||||
return boto3.client('s3', **client_kwargs)
|
||||
else:
|
||||
raise ValueError("Either use_role must be True, or both aws_access_key_id and aws_secret_access_key must be provided")
|
||||
|
||||
def remove_directory(destination, use_role=False, role_name=None, aws_access_key_id=None, aws_secret_access_key=None, endpoint_url=None, region=None):
|
||||
"""
|
||||
Remove a directory recursively, either local or in an S3 bucket
|
||||
"""
|
||||
if destination.startswith('s3://'):
|
||||
# Removing from S3
|
||||
s3_client = get_s3_client(use_role, role_name, aws_access_key_id, aws_secret_access_key, endpoint_url, region)
|
||||
bucket, prefix = parse_s3_path(destination)
|
||||
try:
|
||||
paginator = s3_client.get_paginator('list_objects_v2')
|
||||
for page in paginator.paginate(Bucket=bucket, Prefix=prefix):
|
||||
if 'Contents' in page:
|
||||
objects_to_delete = [{'Key': obj['Key']} for obj in page['Contents']]
|
||||
s3_client.delete_objects(Bucket=bucket, Delete={'Objects': objects_to_delete})
|
||||
print(f"Directory {destination} removed successfully from S3")
|
||||
except ClientError as e:
|
||||
print(f"Error removing directory from S3: {str(e)}")
|
||||
return False
|
||||
else:
|
||||
# Removing local directory
|
||||
try:
|
||||
import shutil
|
||||
if os.path.exists(destination):
|
||||
shutil.rmtree(destination)
|
||||
print(f"Directory {destination} removed successfully")
|
||||
else:
|
||||
print(f"Directory {destination} does not exist")
|
||||
except IOError as e:
|
||||
print(f"Error removing directory: {str(e)}")
|
||||
return False
|
||||
return True
|
||||
|
||||
def parse_s3_path(s3_path):
|
||||
"""
|
||||
Parse an S3 path into bucket and key
|
||||
"""
|
||||
parts = s3_path.replace('s3://', '').split('/', 1)
|
||||
bucket = parts[0]
|
||||
key = parts[1] if len(parts) > 1 else ''
|
||||
return bucket, key
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Remove a directory recursively, either local or in an S3 bucket.")
|
||||
parser.add_argument("destination", help="The directory path (local) or S3 path (e.g., 's3://bucket/prefix') to remove")
|
||||
parser.add_argument("--use_role", action="store_true", help="Use IAM role for authentication")
|
||||
parser.add_argument("--role_name", help="The name of the IAM role to assume")
|
||||
parser.add_argument("--aws_access_key_id", help="AWS access key ID")
|
||||
parser.add_argument("--aws_secret_access_key", help="AWS secret access key")
|
||||
parser.add_argument("--endpoint_url", help="S3-compatible endpoint URL")
|
||||
parser.add_argument("--region", help="AWS region (ignored if endpoint_url is specified)")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.destination.startswith('s3://'):
|
||||
if args.use_role and (args.aws_access_key_id or args.aws_secret_access_key or args.endpoint_url):
|
||||
parser.error("When using IAM role (--use_role), access key, secret, and endpoint URL should not be specified.")
|
||||
|
||||
if (args.aws_access_key_id or args.aws_secret_access_key) and not (args.aws_access_key_id and args.aws_secret_access_key):
|
||||
parser.error("Both --aws_access_key_id and --aws_secret_access_key must be provided when using access key authentication.")
|
||||
|
||||
if not args.use_role and not (args.aws_access_key_id and args.aws_secret_access_key):
|
||||
parser.error("Either --use_role or both --aws_access_key_id and --aws_secret_access_key must be provided for S3 operations.")
|
||||
|
||||
if args.use_role and args.role_name and (args.aws_access_key_id or args.aws_secret_access_key):
|
||||
parser.error("When using a specific role (--role_name), access key and secret should not be specified.")
|
||||
|
||||
success = remove_directory(
|
||||
args.destination,
|
||||
args.use_role,
|
||||
args.role_name,
|
||||
args.aws_access_key_id,
|
||||
args.aws_secret_access_key,
|
||||
args.endpoint_url,
|
||||
args.region
|
||||
)
|
||||
|
||||
if success:
|
||||
print("Cleanup completed successfully.")
|
||||
else:
|
||||
print("Cleanup failed.")
|
||||
exit(1)
|
||||
@@ -0,0 +1,899 @@
|
||||
# The containers configuration file specifies all of the available configuration
|
||||
# command-line options/flags for container engine tools like Podman & Buildah,
|
||||
# but in a TOML format that can be easily modified and versioned.
|
||||
|
||||
# Please refer to containers.conf(5) for details of all configuration options.
|
||||
# Not all container engines implement all of the options.
|
||||
# All of the options have hard coded defaults and these options will override
|
||||
# the built in defaults. Users can then override these options via the command
|
||||
# line. Container engines will read containers.conf files in up to three
|
||||
# locations in the following order:
|
||||
# 1. /usr/share/containers/containers.conf
|
||||
# 2. /etc/containers/containers.conf
|
||||
# 3. $XDG_CONFIG_HOME/containers/containers.conf or
|
||||
# $HOME/.config/containers/containers.conf if $XDG_CONFIG_HOME is not set
|
||||
# Items specified in the latter containers.conf, if they exist, override the
|
||||
# previous containers.conf settings, or the default settings.
|
||||
|
||||
[containers]
|
||||
|
||||
# List of annotation. Specified as
|
||||
# "key = value"
|
||||
# If it is empty or commented out, no annotations will be added
|
||||
#
|
||||
#annotations = []
|
||||
|
||||
# Used to change the name of the default AppArmor profile of container engine.
|
||||
#
|
||||
#apparmor_profile = "container-default"
|
||||
|
||||
# The hosts entries from the base hosts file are added to the containers hosts
|
||||
# file. This must be either an absolute path or as special values "image" which
|
||||
# uses the hosts file from the container image or "none" which means
|
||||
# no base hosts file is used. The default is "" which will use /etc/hosts.
|
||||
#
|
||||
#base_hosts_file = ""
|
||||
|
||||
# List of cgroup_conf entries specifying a list of cgroup files to write to and
|
||||
# their values. For example `memory.high=1073741824` sets the
|
||||
# memory.high limit to 1GB.
|
||||
# cgroup_conf = []
|
||||
|
||||
# Default way to to create a cgroup namespace for the container
|
||||
# Options are:
|
||||
# `private` Create private Cgroup Namespace for the container.
|
||||
# `host` Share host Cgroup Namespace with the container.
|
||||
#
|
||||
#cgroupns = "private"
|
||||
|
||||
# Control container cgroup configuration
|
||||
# Determines whether the container will create CGroups.
|
||||
# Options are:
|
||||
# `enabled` Enable cgroup support within container
|
||||
# `disabled` Disable cgroup support, will inherit cgroups from parent
|
||||
# `no-conmon` Do not create a cgroup dedicated to conmon.
|
||||
#
|
||||
#cgroups = "enabled"
|
||||
|
||||
# List of default capabilities for containers. If it is empty or commented out,
|
||||
# the default capabilities defined in the container engine will be added.
|
||||
#
|
||||
#default_capabilities = [
|
||||
# "CHOWN",
|
||||
# "DAC_OVERRIDE",
|
||||
# "FOWNER",
|
||||
# "FSETID",
|
||||
# "KILL",
|
||||
# "NET_BIND_SERVICE",
|
||||
# "SETFCAP",
|
||||
# "SETGID",
|
||||
# "SETPCAP",
|
||||
# "SETUID",
|
||||
# "SYS_CHROOT",
|
||||
#]
|
||||
|
||||
# A list of sysctls to be set in containers by default,
|
||||
# specified as "name=value",
|
||||
# for example:"net.ipv4.ping_group_range=0 0".
|
||||
#
|
||||
default_sysctls = [
|
||||
"net.ipv4.ping_group_range=0 0",
|
||||
]
|
||||
|
||||
# A list of ulimits to be set in containers by default, specified as
|
||||
# "<ulimit name>=<soft limit>:<hard limit>", for example:
|
||||
# "nofile=1024:2048"
|
||||
# See setrlimit(2) for a list of resource names.
|
||||
# Any limit not specified here will be inherited from the process launching the
|
||||
# container engine.
|
||||
# Ulimits has limits for non privileged container engines.
|
||||
#
|
||||
#default_ulimits = [
|
||||
# "nofile=1280:2560",
|
||||
#]
|
||||
|
||||
# List of devices. Specified as
|
||||
# "<device-on-host>:<device-on-container>:<permissions>", for example:
|
||||
# "/dev/sdc:/dev/xvdc:rwm".
|
||||
# If it is empty or commented out, only the default devices will be used
|
||||
#
|
||||
#devices = []
|
||||
|
||||
# List of default DNS options to be added to /etc/resolv.conf inside of the container.
|
||||
#
|
||||
#dns_options = []
|
||||
|
||||
# List of default DNS search domains to be added to /etc/resolv.conf inside of the container.
|
||||
#
|
||||
#dns_searches = []
|
||||
|
||||
# Set default DNS servers.
|
||||
# This option can be used to override the DNS configuration passed to the
|
||||
# container. The special value "none" can be specified to disable creation of
|
||||
# /etc/resolv.conf in the container.
|
||||
# The /etc/resolv.conf file in the image will be used without changes.
|
||||
#
|
||||
#dns_servers = []
|
||||
|
||||
# Environment variable list for the conmon process; used for passing necessary
|
||||
# environment variables to conmon or the runtime.
|
||||
#
|
||||
#env = [
|
||||
# "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
#]
|
||||
|
||||
# Pass all host environment variables into the container.
|
||||
#
|
||||
#env_host = false
|
||||
|
||||
# Set the ip for the host.containers.internal entry in the containers /etc/hosts
|
||||
# file. This can be set to "none" to disable adding this entry. By default it
|
||||
# will automatically choose the host ip.
|
||||
#
|
||||
# NOTE: When using podman machine this entry will never be added to the containers
|
||||
# hosts file instead the gvproxy dns resolver will resolve this hostname. Therefore
|
||||
# it is not possible to disable the entry in this case.
|
||||
#
|
||||
#host_containers_internal_ip = ""
|
||||
|
||||
# Default proxy environment variables passed into the container.
|
||||
# The environment variables passed in include:
|
||||
# http_proxy, https_proxy, ftp_proxy, no_proxy, and the upper case versions of
|
||||
# these. This option is needed when host system uses a proxy but container
|
||||
# should not use proxy. Proxy environment variables specified for the container
|
||||
# in any other way will override the values passed from the host.
|
||||
#
|
||||
#http_proxy = true
|
||||
|
||||
# Run an init inside the container that forwards signals and reaps processes.
|
||||
#
|
||||
#init = false
|
||||
|
||||
# Container init binary, if init=true, this is the init binary to be used for containers.
|
||||
# If this option is not set catatonit is searched in the directories listed under
|
||||
# the helper_binaries_dir option. It is recommended to just install catatonit
|
||||
# there instead of configuring this option here.
|
||||
#
|
||||
#init_path = "/usr/libexec/podman/catatonit"
|
||||
|
||||
# Default way to to create an IPC namespace (POSIX SysV IPC) for the container
|
||||
# Options are:
|
||||
# "host" Share host IPC Namespace with the container.
|
||||
# "none" Create shareable IPC Namespace for the container without a private /dev/shm.
|
||||
# "private" Create private IPC Namespace for the container, other containers are not allowed to share it.
|
||||
# "shareable" Create shareable IPC Namespace for the container.
|
||||
#
|
||||
#ipcns = "shareable"
|
||||
|
||||
# Default way to set an interface name inside container. Defaults to legacy
|
||||
# pattern of ethX, where X is a integer, when left undefined.
|
||||
# Options are:
|
||||
# "device" Uses the network_interface name from the network config as interface name.
|
||||
# Falls back to the ethX pattern if the network_interface is not set.
|
||||
#interface_name = ""
|
||||
|
||||
# keyring tells the container engine whether to create
|
||||
# a kernel keyring for use within the container.
|
||||
#
|
||||
#keyring = true
|
||||
|
||||
# label tells the container engine whether to use container separation using
|
||||
# MAC(SELinux) labeling or not.
|
||||
# The label flag is ignored on label disabled systems.
|
||||
#
|
||||
#label = true
|
||||
|
||||
# label_users indicates whether to enforce confined users in containers on
|
||||
# SELinux systems. This option causes containers to maintain the current user
|
||||
# and role field of the calling process. By default SELinux containers run with
|
||||
# the user system_u, and the role system_r.
|
||||
#label_users = false
|
||||
|
||||
# Logging driver for the container. Available options: k8s-file and journald.
|
||||
#
|
||||
#log_driver = "k8s-file"
|
||||
|
||||
# Maximum size allowed for the container log file. Negative numbers indicate
|
||||
# that no size limit is imposed. If positive, it must be >= 8192 to match or
|
||||
# exceed conmon's read buffer. The file is truncated and re-opened so the
|
||||
# limit is never exceeded.
|
||||
#
|
||||
#log_size_max = -1
|
||||
|
||||
# Specifies default format tag for container log messages.
|
||||
# This is useful for creating a specific tag for container log messages.
|
||||
# Containers logs default to truncated container ID as a tag.
|
||||
#
|
||||
#log_tag = ""
|
||||
|
||||
# List of mounts. Specified as
|
||||
# "type=TYPE,source=<directory-on-host>,destination=<directory-in-container>,<options>", for example:
|
||||
# "type=bind,source=/var/lib/foobar,destination=/var/lib/foobar,ro".
|
||||
# If it is empty or commented out, no mounts will be added
|
||||
#
|
||||
#mounts = []
|
||||
|
||||
# Default way to to create a Network namespace for the container
|
||||
# Options are:
|
||||
# `private` Create private Network Namespace for the container.
|
||||
# `host` Share host Network Namespace with the container.
|
||||
# `none` Containers do not use the network
|
||||
#
|
||||
#netns = "private"
|
||||
|
||||
# Create /etc/hosts for the container. By default, container engine manage
|
||||
# /etc/hosts, automatically adding the container's own IP address.
|
||||
#
|
||||
#no_hosts = false
|
||||
|
||||
# Tune the host's OOM preferences for containers
|
||||
# (accepts values from -1000 to 1000).
|
||||
#oom_score_adj = 0
|
||||
|
||||
# Default way to to create a PID namespace for the container
|
||||
# Options are:
|
||||
# `private` Create private PID Namespace for the container.
|
||||
# `host` Share host PID Namespace with the container.
|
||||
#
|
||||
#pidns = "private"
|
||||
|
||||
# Maximum number of processes allowed in a container.
|
||||
#
|
||||
#pids_limit = 2048
|
||||
|
||||
# Copy the content from the underlying image into the newly created volume
|
||||
# when the container is created instead of when it is started. If false,
|
||||
# the container engine will not copy the content until the container is started.
|
||||
# Setting it to true may have negative performance implications.
|
||||
#
|
||||
#prepare_volume_on_create = false
|
||||
|
||||
# Give extended privileges to all containers. A privileged container turns off
|
||||
# the security features that isolate the container from the host. Dropped
|
||||
# Capabilities, limited devices, read-only mount points, Apparmor/SELinux
|
||||
# separation, and Seccomp filters are all disabled. Due to the disabled
|
||||
# security features the privileged field should almost never be set as
|
||||
# containers can easily break out of confinment.
|
||||
#
|
||||
# Containers running in a user namespace (e.g., rootless containers) cannot
|
||||
# have more privileges than the user that launched them.
|
||||
#
|
||||
#privileged = false
|
||||
|
||||
# Run all containers with root file system mounted read-only
|
||||
#
|
||||
# read_only = false
|
||||
|
||||
# Path to the seccomp.json profile which is used as the default seccomp profile
|
||||
# for the runtime.
|
||||
#
|
||||
#seccomp_profile = "/usr/share/containers/seccomp.json"
|
||||
|
||||
# Size of /dev/shm. Specified as <number><unit>.
|
||||
# Unit is optional, values:
|
||||
# b (bytes), k (kilobytes), m (megabytes), or g (gigabytes).
|
||||
# If the unit is omitted, the system uses bytes.
|
||||
#
|
||||
#shm_size = "65536k"
|
||||
|
||||
# Set timezone in container. Takes IANA timezones as well as "local",
|
||||
# which sets the timezone in the container to match the host machine.
|
||||
#
|
||||
#tz = ""
|
||||
|
||||
# Set umask inside the container
|
||||
#
|
||||
#umask = "0022"
|
||||
|
||||
# Default way to to create a User namespace for the container
|
||||
# Options are:
|
||||
# `auto` Create unique User Namespace for the container.
|
||||
# `host` Share host User Namespace with the container.
|
||||
#
|
||||
#userns = "host"
|
||||
|
||||
# Default way to to create a UTS namespace for the container
|
||||
# Options are:
|
||||
# `private` Create private UTS Namespace for the container.
|
||||
# `host` Share host UTS Namespace with the container.
|
||||
#
|
||||
#utsns = "private"
|
||||
|
||||
# List of volumes. Specified as
|
||||
# "<directory-on-host>:<directory-in-container>:<options>", for example:
|
||||
# "/db:/var/lib/db:ro".
|
||||
# If it is empty or commented out, no volumes will be added
|
||||
#
|
||||
#volumes = []
|
||||
|
||||
#[engine.platform_to_oci_runtime]
|
||||
#"wasi/wasm" = ["crun-wasm"]
|
||||
#"wasi/wasm32" = ["crun-wasm"]
|
||||
#"wasi/wasm64" = ["crun-wasm"]
|
||||
|
||||
[secrets]
|
||||
#driver = "file"
|
||||
|
||||
[secrets.opts]
|
||||
#root = "/example/directory"
|
||||
|
||||
[network]
|
||||
|
||||
# Network backend determines what network driver will be used to set up and tear down container networks.
|
||||
# Valid values are "cni" and "netavark".
|
||||
# The default value is empty which means that it will automatically choose CNI or netavark. If there are
|
||||
# already containers/images or CNI networks preset it will choose CNI.
|
||||
#
|
||||
# Before changing this value all containers must be stopped otherwise it is likely that
|
||||
# iptables rules and network interfaces might leak on the host. A reboot will fix this.
|
||||
#
|
||||
network_backend = "cni"
|
||||
|
||||
# Path to directory where CNI plugin binaries are located.
|
||||
#
|
||||
#cni_plugin_dirs = [
|
||||
# "/usr/local/libexec/cni",
|
||||
# "/usr/libexec/cni",
|
||||
# "/usr/local/lib/cni",
|
||||
# "/usr/lib/cni",
|
||||
# "/opt/cni/bin",
|
||||
#]
|
||||
|
||||
# List of directories that will be searched for netavark plugins.
|
||||
#
|
||||
#netavark_plugin_dirs = [
|
||||
# "/usr/local/libexec/netavark",
|
||||
# "/usr/libexec/netavark",
|
||||
# "/usr/local/lib/netavark",
|
||||
# "/usr/lib/netavark",
|
||||
#]
|
||||
|
||||
# The firewall driver to be used by netavark.
|
||||
# The default is empty which means netavark will pick one accordingly. Current supported
|
||||
# drivers are "iptables", "nftables", "none" (no firewall rules will be created) and "firewalld" (firewalld is
|
||||
# experimental at the moment and not recommend outside of testing).
|
||||
#
|
||||
#firewall_driver = ""
|
||||
|
||||
|
||||
# The network name of the default network to attach pods to.
|
||||
#
|
||||
#default_network = "podman"
|
||||
|
||||
# The default subnet for the default network given in default_network.
|
||||
# If a network with that name does not exist, a new network using that name and
|
||||
# this subnet will be created.
|
||||
# Must be a valid IPv4 CIDR prefix.
|
||||
#
|
||||
#default_subnet = "10.88.0.0/16"
|
||||
|
||||
# DefaultSubnetPools is a list of subnets and size which are used to
|
||||
# allocate subnets automatically for podman network create.
|
||||
# It will iterate through the list and will pick the first free subnet
|
||||
# with the given size. This is only used for ipv4 subnets, ipv6 subnets
|
||||
# are always assigned randomly.
|
||||
#
|
||||
#default_subnet_pools = [
|
||||
# {"base" = "10.89.0.0/16", "size" = 24},
|
||||
# {"base" = "10.90.0.0/15", "size" = 24},
|
||||
# {"base" = "10.92.0.0/14", "size" = 24},
|
||||
# {"base" = "10.96.0.0/11", "size" = 24},
|
||||
# {"base" = "10.128.0.0/9", "size" = 24},
|
||||
#]
|
||||
|
||||
|
||||
|
||||
# Configure which rootless network program to use by default. Valid options are
|
||||
# `slirp4netns` and `pasta` (default).
|
||||
#
|
||||
#default_rootless_network_cmd = "pasta"
|
||||
|
||||
# Path to the directory where network configuration files are located.
|
||||
# For the CNI backend the default is "/etc/cni/net.d" as root
|
||||
# and "$HOME/.config/cni/net.d" as rootless.
|
||||
# For the netavark backend "/etc/containers/networks" is used as root
|
||||
# and "$graphroot/networks" as rootless.
|
||||
#
|
||||
#network_config_dir = "/etc/cni/net.d/"
|
||||
|
||||
# Port to use for dns forwarding daemon with netavark in rootful bridge
|
||||
# mode and dns enabled.
|
||||
# Using an alternate port might be useful if other dns services should
|
||||
# run on the machine.
|
||||
#
|
||||
#dns_bind_port = 53
|
||||
|
||||
# A list of default pasta options that should be used running pasta.
|
||||
# It accepts the pasta cli options, see pasta(1) for the full list of options.
|
||||
#
|
||||
#pasta_options = []
|
||||
|
||||
[engine]
|
||||
# Index to the active service
|
||||
#
|
||||
#active_service = "production"
|
||||
|
||||
#List of compression algorithms. If set makes sure that requested compression variant
|
||||
#for each platform is added to the manifest list keeping original instance intact in
|
||||
#the same manifest list on every `manifest push`. Supported values are (`gzip`, `zstd` and `zstd:chunked`).
|
||||
#
|
||||
#add_compression = ["gzip", "zstd", "zstd:chunked"]
|
||||
|
||||
# Enforces using docker.io for completing short names in Podman's compatibility
|
||||
# REST API. Note that this will ignore unqualified-search-registries and
|
||||
# short-name aliases defined in containers-registries.conf(5).
|
||||
#compat_api_enforce_docker_hub = true
|
||||
|
||||
# Specify one or more external providers for the compose command. The first
|
||||
# found provider is used for execution. Can be an absolute and relative path
|
||||
# or a (file) name.
|
||||
#compose_providers=[]
|
||||
|
||||
# Emit logs on each invocation of the compose command indicating that an
|
||||
# external compose provider is being executed.
|
||||
#compose_warning_logs = true
|
||||
|
||||
# The compression format to use when pushing an image.
|
||||
# Valid options are: `gzip`, `zstd` and `zstd:chunked`.
|
||||
# This field is ignored when pushing images to the docker-daemon and
|
||||
# docker-archive formats. It is also ignored when the manifest format is set
|
||||
# to v2s2.
|
||||
#
|
||||
#compression_format = "gzip"
|
||||
|
||||
# The compression level to use when pushing an image.
|
||||
# Valid options depend on the compression format used.
|
||||
# For gzip, valid options are 1-9, with a default of 5.
|
||||
# For zstd, valid options are 1-20, with a default of 3.
|
||||
#
|
||||
#compression_level = 5
|
||||
|
||||
# Cgroup management implementation used for the runtime.
|
||||
# Valid options "systemd" or "cgroupfs"
|
||||
#
|
||||
#cgroup_manager = "systemd"
|
||||
|
||||
# Environment variables to pass into conmon
|
||||
#
|
||||
#conmon_env_vars = [
|
||||
# "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
#]
|
||||
|
||||
# Paths to look for the conmon container manager binary
|
||||
#
|
||||
#conmon_path = [
|
||||
# "/usr/libexec/podman/conmon",
|
||||
# "/usr/local/libexec/podman/conmon",
|
||||
# "/usr/local/lib/podman/conmon",
|
||||
# "/usr/bin/conmon",
|
||||
# "/usr/sbin/conmon",
|
||||
# "/usr/local/bin/conmon",
|
||||
# "/usr/local/sbin/conmon"
|
||||
#]
|
||||
|
||||
# Enforces using docker.io for completing short names in Podman's compatibility
|
||||
# REST API. Note that this will ignore unqualified-search-registries and
|
||||
# short-name aliases defined in containers-registries.conf(5).
|
||||
#compat_api_enforce_docker_hub = true
|
||||
|
||||
# The database backend of Podman. Supported values are "" (default), "boltdb"
|
||||
# and "sqlite". An empty value means it will check whenever a boltdb already
|
||||
# exists and use it when it does, otherwise it will use sqlite as default
|
||||
# (e.g. new installs). This allows for backwards compatibility with older versions.
|
||||
# Please run `podman-system-reset` prior to changing the database
|
||||
# backend of an existing deployment, to make sure Podman can operate correctly.
|
||||
#
|
||||
#database_backend = ""
|
||||
|
||||
# Specify the keys sequence used to detach a container.
|
||||
# Format is a single character [a-Z] or a comma separated sequence of
|
||||
# `ctrl-<value>`, where `<value>` is one of:
|
||||
# `a-z`, `@`, `^`, `[`, `\`, `]`, `^` or `_`
|
||||
# Specifying "" disables this feature.
|
||||
#detach_keys = "ctrl-p,ctrl-q"
|
||||
|
||||
# Determines whether engine will reserve ports on the host when they are
|
||||
# forwarded to containers. When enabled, when ports are forwarded to containers,
|
||||
# ports are held open by as long as the container is running, ensuring that
|
||||
# they cannot be reused by other programs on the host. However, this can cause
|
||||
# significant memory usage if a container has many ports forwarded to it.
|
||||
# Disabling this can save memory.
|
||||
#
|
||||
#enable_port_reservation = true
|
||||
|
||||
# Environment variables to be used when running the container engine (e.g., Podman, Buildah).
|
||||
# For example "http_proxy=internal.proxy.company.com".
|
||||
# Note these environment variables will not be used within the container.
|
||||
# Set the env section under [containers] table, if you want to set environment variables for the container.
|
||||
#
|
||||
#env = []
|
||||
|
||||
# Define where event logs will be stored, when events_logger is "file".
|
||||
#events_logfile_path=""
|
||||
|
||||
# Sets the maximum size for events_logfile_path.
|
||||
# The size can be b (bytes), k (kilobytes), m (megabytes), or g (gigabytes).
|
||||
# The format for the size is `<number><unit>`, e.g., `1b` or `3g`.
|
||||
# If no unit is included then the size will be read in bytes.
|
||||
# When the limit is exceeded, the logfile will be rotated and the old one will be deleted.
|
||||
# If the maximum size is set to 0, then no limit will be applied,
|
||||
# and the logfile will not be rotated.
|
||||
#events_logfile_max_size = "1m"
|
||||
|
||||
# Selects which logging mechanism to use for container engine events.
|
||||
# Valid values are `journald`, `file` and `none`.
|
||||
#
|
||||
#events_logger = "journald"
|
||||
|
||||
# Creates a more verbose container-create event which includes a JSON payload
|
||||
# with detailed information about the container.
|
||||
#events_container_create_inspect_data = false
|
||||
|
||||
# Whenever Podman should log healthcheck events.
|
||||
# With many running healthcheck on short interval Podman will spam the event
|
||||
# log a lot as it generates a event for each single healthcheck run. Because
|
||||
# this event is optional and only useful to external consumers that may want
|
||||
# to know when a healthcheck is run or failed allow users to turn it off by
|
||||
# setting it to false. The default is true.
|
||||
#
|
||||
#healthcheck_events = true
|
||||
|
||||
# A is a list of directories which are used to search for helper binaries.
|
||||
#
|
||||
#helper_binaries_dir = [
|
||||
# "/usr/local/libexec/podman",
|
||||
# "/usr/local/lib/podman",
|
||||
# "/usr/libexec/podman",
|
||||
# "/usr/lib/podman",
|
||||
#]
|
||||
|
||||
# Path to OCI hooks directories for automatically executed hooks.
|
||||
#
|
||||
#hooks_dir = [
|
||||
# "/usr/share/containers/oci/hooks.d",
|
||||
#]
|
||||
|
||||
# Directories to scan for CDI Spec files.
|
||||
#
|
||||
#cdi_spec_dirs = [
|
||||
# "/etc/cdi",
|
||||
#]
|
||||
|
||||
# Manifest Type (oci, v2s2, or v2s1) to use when pulling, pushing, building
|
||||
# container images. By default image pulled and pushed match the format of the
|
||||
# source image. Building/committing defaults to OCI.
|
||||
#
|
||||
#image_default_format = ""
|
||||
|
||||
# Default transport method for pulling and pushing for images
|
||||
#
|
||||
#image_default_transport = "docker://"
|
||||
|
||||
# Maximum number of image layers to be copied (pulled/pushed) simultaneously.
|
||||
# Not setting this field, or setting it to zero, will fall back to containers/image defaults.
|
||||
#
|
||||
#image_parallel_copies = 0
|
||||
|
||||
# Tells container engines how to handle the built-in image volumes.
|
||||
# * anonymous: An anonymous named volume will be created and mounted
|
||||
# into the container.
|
||||
# * tmpfs: The volume is mounted onto the container as a tmpfs,
|
||||
# which allows users to create content that disappears when
|
||||
# the container is stopped.
|
||||
# * ignore: All volumes are just ignored and no action is taken.
|
||||
#
|
||||
#image_volume_mode = ""
|
||||
|
||||
# Default command to run the infra container
|
||||
#
|
||||
#infra_command = "/pause"
|
||||
|
||||
# Infra (pause) container image name for pod infra containers. When running a
|
||||
# pod, we start a `pause` process in a container to hold open the namespaces
|
||||
# associated with the pod. This container does nothing other than sleep,
|
||||
# reserving the pod's resources for the lifetime of the pod. By default container
|
||||
# engines run a built-in container using the pause executable. If you want override
|
||||
# specify an image to pull.
|
||||
#
|
||||
#infra_image = ""
|
||||
|
||||
# Default Kubernetes kind/specification of the kubernetes yaml generated with the `podman kube generate` command.
|
||||
# The possible options are `pod` and `deployment`.
|
||||
#kube_generate_type = "pod"
|
||||
|
||||
# Specify the locking mechanism to use; valid values are "shm" and "file".
|
||||
# Change the default only if you are sure of what you are doing, in general
|
||||
# "file" is useful only on platforms where cgo is not available for using the
|
||||
# faster "shm" lock type. You may need to run "podman system renumber" after
|
||||
# you change the lock type.
|
||||
#
|
||||
#lock_type = "shm"
|
||||
|
||||
# MultiImageArchive - if true, the container engine allows for storing archives
|
||||
# (e.g., of the docker-archive transport) with multiple images. By default,
|
||||
# Podman creates single-image archives.
|
||||
#
|
||||
#multi_image_archive = false
|
||||
|
||||
# Default engine namespace
|
||||
# If engine is joined to a namespace, it will see only containers and pods
|
||||
# that were created in the same namespace, and will create new containers and
|
||||
# pods in that namespace.
|
||||
# The default namespace is "", which corresponds to no namespace. When no
|
||||
# namespace is set, all containers and pods are visible.
|
||||
#
|
||||
#namespace = ""
|
||||
|
||||
# Path to the slirp4netns binary
|
||||
#
|
||||
#network_cmd_path = ""
|
||||
|
||||
# Default options to pass to the slirp4netns binary.
|
||||
# Valid options values are:
|
||||
#
|
||||
# - allow_host_loopback=true|false: Allow the slirp4netns to reach the host loopback IP (`10.0.2.2`).
|
||||
# Default is false.
|
||||
# - mtu=MTU: Specify the MTU to use for this network. (Default is `65520`).
|
||||
# - cidr=CIDR: Specify ip range to use for this network. (Default is `10.0.2.0/24`).
|
||||
# - enable_ipv6=true|false: Enable IPv6. Default is true. (Required for `outbound_addr6`).
|
||||
# - outbound_addr=INTERFACE: Specify the outbound interface slirp should bind to (ipv4 traffic only).
|
||||
# - outbound_addr=IPv4: Specify the outbound ipv4 address slirp should bind to.
|
||||
# - outbound_addr6=INTERFACE: Specify the outbound interface slirp should bind to (ipv6 traffic only).
|
||||
# - outbound_addr6=IPv6: Specify the outbound ipv6 address slirp should bind to.
|
||||
# - port_handler=rootlesskit: Use rootlesskit for port forwarding. Default.
|
||||
# Note: Rootlesskit changes the source IP address of incoming packets to a IP address in the container
|
||||
# network namespace, usually `10.0.2.100`. If your application requires the real source IP address,
|
||||
# e.g. web server logs, use the slirp4netns port handler. The rootlesskit port handler is also used for
|
||||
# rootless containers when connected to user-defined networks.
|
||||
# - port_handler=slirp4netns: Use the slirp4netns port forwarding, it is slower than rootlesskit but
|
||||
# preserves the correct source IP address. This port handler cannot be used for user-defined networks.
|
||||
#
|
||||
#network_cmd_options = []
|
||||
|
||||
# Whether to use chroot instead of pivot_root in the runtime
|
||||
#
|
||||
#no_pivot_root = false
|
||||
|
||||
# Number of locks available for containers, pods, and volumes. Each container,
|
||||
# pod, and volume consumes 1 lock for as long as it exists.
|
||||
# If this is changed, a lock renumber must be performed (e.g. with the
|
||||
# 'podman system renumber' command).
|
||||
#
|
||||
#num_locks = 2048
|
||||
|
||||
# Set the exit policy of the pod when the last container exits.
|
||||
#pod_exit_policy = "continue"
|
||||
|
||||
# Whether to pull new image before running a container
|
||||
#
|
||||
#pull_policy = "missing"
|
||||
|
||||
# Indicates whether the application should be running in remote mode. This flag modifies the
|
||||
# --remote option on container engines. Setting the flag to true will default
|
||||
# `podman --remote=true` for access to the remote Podman service.
|
||||
#
|
||||
#remote = false
|
||||
|
||||
# Number of times to retry pulling/pushing images in case of failure
|
||||
#
|
||||
#retry = 3
|
||||
|
||||
# Delay between retries in case pulling/pushing image fails.
|
||||
# If set, container engines will retry at the set interval,
|
||||
# otherwise they delay 2 seconds and then exponentially back off.
|
||||
#
|
||||
#retry_delay = "2s"
|
||||
|
||||
# Default OCI runtime
|
||||
#
|
||||
#runtime = "crun"
|
||||
|
||||
# List of the OCI runtimes that support --format=json. When json is supported
|
||||
# engine will use it for reporting nicer errors.
|
||||
#
|
||||
#runtime_supports_json = ["crun", "runc", "kata", "runsc", "youki", "krun"]
|
||||
|
||||
# List of the OCI runtimes that supports running containers with KVM Separation.
|
||||
#
|
||||
#runtime_supports_kvm = ["kata", "krun"]
|
||||
|
||||
# List of the OCI runtimes that supports running containers without cgroups.
|
||||
#
|
||||
#runtime_supports_nocgroups = ["crun", "krun"]
|
||||
|
||||
# Default location for storing temporary container image content. Can be overridden with the TMPDIR environment
|
||||
# variable. If you specify "storage", then the location of the
|
||||
# container/storage tmp directory will be used.
|
||||
# image_copy_tmp_dir="/var/tmp"
|
||||
|
||||
# Number of seconds to wait without a connection
|
||||
# before the `podman system service` times out and exits
|
||||
#
|
||||
#service_timeout = 5
|
||||
|
||||
# Directory for persistent engine files (database, etc)
|
||||
# By default, this will be configured relative to where the containers/storage
|
||||
# stores containers
|
||||
# Uncomment to change location from this default
|
||||
#
|
||||
#static_dir = "/var/lib/containers/storage/libpod"
|
||||
|
||||
# Number of seconds to wait for container to exit before sending kill signal.
|
||||
#
|
||||
#stop_timeout = 10
|
||||
|
||||
# Number of seconds to wait before exit command in API process is given to.
|
||||
# This mimics Docker's exec cleanup behaviour, where the default is 5 minutes (value is in seconds).
|
||||
#
|
||||
#exit_command_delay = 300
|
||||
|
||||
# map of service destinations
|
||||
#
|
||||
# [engine.service_destinations]
|
||||
# [engine.service_destinations.production]
|
||||
# URI to access the Podman service
|
||||
# Examples:
|
||||
# rootless "unix:///run/user/$UID/podman/podman.sock" (Default)
|
||||
# rootful "unix:///run/podman/podman.sock (Default)
|
||||
# remote rootless ssh://engineering.lab.company.com/run/user/1000/podman/podman.sock
|
||||
# remote rootful ssh://root@10.10.1.136:22/run/podman/podman.sock
|
||||
#
|
||||
# uri = "ssh://user@production.example.com/run/user/1001/podman/podman.sock"
|
||||
# Path to file containing ssh identity key
|
||||
# identity = "~/.ssh/id_rsa"
|
||||
|
||||
# Directory for temporary files. Must be tmpfs (wiped after reboot)
|
||||
#
|
||||
#tmp_dir = "/run/libpod"
|
||||
|
||||
# Directory for libpod named volumes.
|
||||
# By default, this will be configured relative to where containers/storage
|
||||
# stores containers.
|
||||
# Uncomment to change location from this default.
|
||||
#
|
||||
#volume_path = "/var/lib/containers/storage/volumes"
|
||||
|
||||
# Default timeout (in seconds) for volume plugin operations.
|
||||
# Plugins are external programs accessed via a REST API; this sets a timeout
|
||||
# for requests to that API.
|
||||
# A value of 0 is treated as no timeout.
|
||||
#volume_plugin_timeout = 5
|
||||
|
||||
# Paths to look for a valid OCI runtime (crun, runc, kata, runsc, krun, etc)
|
||||
[engine.runtimes]
|
||||
#crun = [
|
||||
# "/usr/bin/crun",
|
||||
# "/usr/sbin/crun",
|
||||
# "/usr/local/bin/crun",
|
||||
# "/usr/local/sbin/crun",
|
||||
# "/sbin/crun",
|
||||
# "/bin/crun",
|
||||
# "/run/current-system/sw/bin/crun",
|
||||
#]
|
||||
|
||||
#crun-vm = [
|
||||
# "/usr/bin/crun-vm",
|
||||
# "/usr/local/bin/crun-vm",
|
||||
# "/usr/local/sbin/crun-vm",
|
||||
# "/sbin/crun-vm",
|
||||
# "/bin/crun-vm",
|
||||
# "/run/current-system/sw/bin/crun-vm",
|
||||
#]
|
||||
|
||||
#kata = [
|
||||
# "/usr/bin/kata-runtime",
|
||||
# "/usr/sbin/kata-runtime",
|
||||
# "/usr/local/bin/kata-runtime",
|
||||
# "/usr/local/sbin/kata-runtime",
|
||||
# "/sbin/kata-runtime",
|
||||
# "/bin/kata-runtime",
|
||||
# "/usr/bin/kata-qemu",
|
||||
# "/usr/bin/kata-fc",
|
||||
#]
|
||||
|
||||
#runc = [
|
||||
# "/usr/bin/runc",
|
||||
# "/usr/sbin/runc",
|
||||
# "/usr/local/bin/runc",
|
||||
# "/usr/local/sbin/runc",
|
||||
# "/sbin/runc",
|
||||
# "/bin/runc",
|
||||
# "/usr/lib/cri-o-runc/sbin/runc",
|
||||
#]
|
||||
|
||||
#runsc = [
|
||||
# "/usr/bin/runsc",
|
||||
# "/usr/sbin/runsc",
|
||||
# "/usr/local/bin/runsc",
|
||||
# "/usr/local/sbin/runsc",
|
||||
# "/bin/runsc",
|
||||
# "/sbin/runsc",
|
||||
# "/run/current-system/sw/bin/runsc",
|
||||
#]
|
||||
|
||||
#youki = [
|
||||
# "/usr/local/bin/youki",
|
||||
# "/usr/bin/youki",
|
||||
# "/bin/youki",
|
||||
# "/run/current-system/sw/bin/youki",
|
||||
#]
|
||||
|
||||
#krun = [
|
||||
# "/usr/bin/krun",
|
||||
# "/usr/local/bin/krun",
|
||||
#]
|
||||
|
||||
[engine.volume_plugins]
|
||||
#testplugin = "/run/podman/plugins/test.sock"
|
||||
|
||||
[machine]
|
||||
# Number of CPU's a machine is created with.
|
||||
#
|
||||
#cpus=1
|
||||
|
||||
# The size of the disk in GB created when init-ing a podman-machine VM.
|
||||
#
|
||||
#disk_size=10
|
||||
|
||||
# Default Image used when creating a new VM using `podman machine init`.
|
||||
# Can be specified as registry with a bootable OCI artifact, download URL, or a local path.
|
||||
# Registry target must be in the form of `docker://registry/repo/image:version`.
|
||||
# Container engines translate URIs $OS and $ARCH to the native OS and ARCH.
|
||||
# URI "https://example.com/$OS/$ARCH/foobar.ami" would become
|
||||
# "https://example.com/linux/amd64/foobar.ami" on a Linux AMD machine.
|
||||
# If unspecified, the default Podman machine image will be used.
|
||||
#
|
||||
#image = ""
|
||||
|
||||
# Memory in MB a machine is created with.
|
||||
#
|
||||
#memory=2048
|
||||
|
||||
# The username to use and create on the podman machine OS for rootless
|
||||
# container access.
|
||||
#
|
||||
#user = "core"
|
||||
|
||||
# Host directories to be mounted as volumes into the VM by default.
|
||||
# Environment variables like $HOME as well as complete paths are supported for
|
||||
# the source and destination. An optional third field `:ro` can be used to
|
||||
# tell the container engines to mount the volume readonly.
|
||||
#
|
||||
#volumes = [
|
||||
# "$HOME:$HOME",
|
||||
#]
|
||||
|
||||
# Virtualization provider used to run Podman machine.
|
||||
# If it is empty or commented out, the default provider will be used.
|
||||
#
|
||||
#provider = ""
|
||||
|
||||
# Rosetta supports running x86_64 Linux binaries on a Podman machine on Apple silicon.
|
||||
# The default value is `true`. Supported on AppleHV(arm64) machines only.
|
||||
#
|
||||
#rosetta=true
|
||||
|
||||
# The [machine] table MUST be the last entry in this file.
|
||||
# (Unless another table is added)
|
||||
# TOML does not provide a way to end a table other than a further table being
|
||||
# defined, so every key hereafter will be part of [machine] and not the
|
||||
# main config.
|
||||
|
||||
[farms]
|
||||
#
|
||||
# the default farm to use when farming out builds
|
||||
# default = ""
|
||||
#
|
||||
# map of existing farms
|
||||
#[farms.list]
|
||||
|
||||
[podmansh]
|
||||
# Shell to spawn in container. Default: /bin/sh.
|
||||
#shell = "/bin/sh"
|
||||
#
|
||||
# Name of the container the podmansh user should join.
|
||||
#container = "podmansh"
|
||||
#
|
||||
# Default timeout in seconds for podmansh logins.
|
||||
# Favored over the deprecated "podmansh_timeout" field.
|
||||
#timeout = 30
|
||||
Executable
+119
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import boto3
|
||||
import argparse
|
||||
from botocore.exceptions import ClientError
|
||||
|
||||
def get_s3_client(use_role=False, role_name=None, aws_access_key_id=None, aws_secret_access_key=None, endpoint_url=None, region=None):
|
||||
"""
|
||||
Create and return an S3 client based on the provided authentication method, endpoint, and region.
|
||||
"""
|
||||
client_kwargs = {}
|
||||
|
||||
if endpoint_url:
|
||||
client_kwargs['endpoint_url'] = endpoint_url
|
||||
elif region:
|
||||
client_kwargs['region_name'] = region
|
||||
|
||||
if use_role:
|
||||
if role_name:
|
||||
# Assume the specified role
|
||||
sts_client = boto3.client('sts')
|
||||
assumed_role_object = sts_client.assume_role(
|
||||
RoleArn=f"arn:aws:iam::{boto3.client('sts').get_caller_identity()['Account']}:role/{role_name}",
|
||||
RoleSessionName="AssumeRoleSession"
|
||||
)
|
||||
credentials = assumed_role_object['Credentials']
|
||||
client_kwargs['aws_access_key_id'] = credentials['AccessKeyId']
|
||||
client_kwargs['aws_secret_access_key'] = credentials['SecretAccessKey']
|
||||
client_kwargs['aws_session_token'] = credentials['SessionToken']
|
||||
return boto3.client('s3', **client_kwargs)
|
||||
elif aws_access_key_id and aws_secret_access_key:
|
||||
client_kwargs['aws_access_key_id'] = aws_access_key_id
|
||||
client_kwargs['aws_secret_access_key'] = aws_secret_access_key
|
||||
return boto3.client('s3', **client_kwargs)
|
||||
else:
|
||||
raise ValueError("Either use_role must be True, or both aws_access_key_id and aws_secret_access_key must be provided")
|
||||
|
||||
def transfer_file(source, destination, use_role=False, role_name=None, aws_access_key_id=None, aws_secret_access_key=None, endpoint_url=None, region=None):
|
||||
"""
|
||||
Transfer a file from a local source to either a local destination or an S3 bucket
|
||||
"""
|
||||
if not os.path.isfile(source):
|
||||
print(f"Error: Source file '{source}' does not exist or is not a file.")
|
||||
return False
|
||||
|
||||
if destination.startswith('s3://'):
|
||||
# Uploading to S3
|
||||
s3_client = get_s3_client(use_role, role_name, aws_access_key_id, aws_secret_access_key, endpoint_url, region)
|
||||
bucket, s3_key = parse_s3_path(destination)
|
||||
try:
|
||||
s3_client.upload_file(source, bucket, s3_key)
|
||||
print(f"File {source} uploaded successfully to {destination}")
|
||||
except ClientError as e:
|
||||
print(f"Error uploading file: {str(e)}")
|
||||
return False
|
||||
else:
|
||||
# Copying to local destination
|
||||
try:
|
||||
import shutil
|
||||
# Create destination directory if it doesn't exist
|
||||
os.makedirs(os.path.dirname(destination), exist_ok=True)
|
||||
shutil.copy2(source, destination)
|
||||
print(f"File {source} copied successfully to {destination}")
|
||||
except IOError as e:
|
||||
print(f"Error copying file: {str(e)}")
|
||||
return False
|
||||
return True
|
||||
|
||||
def parse_s3_path(s3_path):
|
||||
"""
|
||||
Parse an S3 path into bucket and key
|
||||
"""
|
||||
parts = s3_path.replace('s3://', '').split('/', 1)
|
||||
bucket = parts[0]
|
||||
key = parts[1] if len(parts) > 1 else ''
|
||||
return bucket, key
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Transfer a file from a local source to either a local destination or an S3 bucket.")
|
||||
parser.add_argument("source", help="The local source file path")
|
||||
parser.add_argument("destination", help="The destination file path (local) or S3 path (e.g., 's3://bucket/key')")
|
||||
parser.add_argument("--use_role", action="store_true", help="Use IAM role for authentication")
|
||||
parser.add_argument("--role_name", help="The name of the IAM role to assume")
|
||||
parser.add_argument("--aws_access_key_id", help="AWS access key ID")
|
||||
parser.add_argument("--aws_secret_access_key", help="AWS secret access key")
|
||||
parser.add_argument("--endpoint_url", help="S3-compatible endpoint URL")
|
||||
parser.add_argument("--region", help="AWS region (ignored if endpoint_url is specified)")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.destination.startswith('s3://'):
|
||||
if args.use_role and (args.aws_access_key_id or args.aws_secret_access_key or args.endpoint_url):
|
||||
parser.error("When using IAM role (--use_role), access key, secret, and endpoint URL should not be specified.")
|
||||
|
||||
if (args.aws_access_key_id or args.aws_secret_access_key) and not (args.aws_access_key_id and args.aws_secret_access_key):
|
||||
parser.error("Both --aws_access_key_id and --aws_secret_access_key must be provided when using access key authentication.")
|
||||
|
||||
if not args.use_role and not (args.aws_access_key_id and args.aws_secret_access_key):
|
||||
parser.error("Either --use_role or both --aws_access_key_id and --aws_secret_access_key must be provided for S3 transfers.")
|
||||
|
||||
if args.use_role and args.role_name and (args.aws_access_key_id or args.aws_secret_access_key):
|
||||
parser.error("When using a specific role (--role_name), access key and secret should not be specified.")
|
||||
|
||||
success = transfer_file(
|
||||
args.source,
|
||||
args.destination,
|
||||
args.use_role,
|
||||
args.role_name,
|
||||
args.aws_access_key_id,
|
||||
args.aws_secret_access_key,
|
||||
args.endpoint_url,
|
||||
args.region
|
||||
)
|
||||
|
||||
if success:
|
||||
print("Transfer completed successfully.")
|
||||
else:
|
||||
print("Transfer failed.")
|
||||
exit(1)
|
||||
@@ -0,0 +1 @@
|
||||
unqualified-search-registries = ["docker.io"]
|
||||
@@ -0,0 +1,3 @@
|
||||
boto3
|
||||
botocore
|
||||
jmespath
|
||||
@@ -0,0 +1,5 @@
|
||||
[storage]
|
||||
driver = "overlay"
|
||||
|
||||
[storage.options.overlay]
|
||||
mount_program = "/usr/bin/fuse-overlayfs"
|
||||
@@ -0,0 +1,99 @@
|
||||
module github.com/lukaszraczylo/kubernetes-images-sync-operator
|
||||
|
||||
go 1.22.0
|
||||
|
||||
require (
|
||||
github.com/go-logr/logr v1.4.2
|
||||
github.com/onsi/ginkgo/v2 v2.19.0
|
||||
github.com/onsi/gomega v1.33.1
|
||||
google.golang.org/grpc v1.65.0
|
||||
k8s.io/api v0.31.0
|
||||
k8s.io/apimachinery v0.31.0
|
||||
k8s.io/client-go v0.31.0
|
||||
k8s.io/cri-api v0.31.0
|
||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
|
||||
sigs.k8s.io/controller-runtime v0.19.0
|
||||
)
|
||||
|
||||
require (
|
||||
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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // 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.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.4 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.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/cel-go v0.20.1 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
||||
github.com/imdario/mergo v0.3.6 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/prometheus/client_golang v1.19.1 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.55.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/spf13/cobra v1.8.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stoewer/go-strcase v1.2.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
|
||||
go.opentelemetry.io/otel v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.26.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect
|
||||
golang.org/x/net v0.26.0 // indirect
|
||||
golang.org/x/oauth2 v0.21.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/term v0.21.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.31.0 // indirect
|
||||
k8s.io/apiserver v0.31.0 // indirect
|
||||
k8s.io/component-base v0.31.0 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
)
|
||||
@@ -0,0 +1,253 @@
|
||||
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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k=
|
||||
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
|
||||
github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
|
||||
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
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-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-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 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
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.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
|
||||
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/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/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84=
|
||||
github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
|
||||
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
||||
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/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-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM=
|
||||
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
|
||||
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
|
||||
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
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/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/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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
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/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.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
|
||||
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
|
||||
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
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.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||
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.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
|
||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
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/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
|
||||
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
|
||||
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/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/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
|
||||
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
|
||||
go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE=
|
||||
go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
|
||||
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
|
||||
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||
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/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU=
|
||||
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
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/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.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
|
||||
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
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.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
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.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
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/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/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.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo=
|
||||
k8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE=
|
||||
k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk=
|
||||
k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk=
|
||||
k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc=
|
||||
k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
|
||||
k8s.io/apiserver v0.31.0 h1:p+2dgJjy+bk+B1Csz+mc2wl5gHwvNkC9QJV+w55LVrY=
|
||||
k8s.io/apiserver v0.31.0/go.mod h1:KI9ox5Yu902iBnnyMmy7ajonhKnkeZYJhTZ/YI+WEMk=
|
||||
k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8=
|
||||
k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU=
|
||||
k8s.io/component-base v0.31.0 h1:/KIzGM5EvPNQcYgwq5NwoQBaOlVFrghoVGr8lG6vNRs=
|
||||
k8s.io/component-base v0.31.0/go.mod h1:TYVuzI1QmN4L5ItVdMSXKvH7/DtvIuas5/mm8YT3rTo=
|
||||
k8s.io/cri-api v0.31.0 h1:6o0XrhWlc1/zseGCh+aMScdXCg5nT6KCGdyx7HQkSKo=
|
||||
k8s.io/cri-api v0.31.0/go.mod h1:Po3TMAYH/+KrZabi7QiwQI4a692oZcUOUThd/rqwxrI=
|
||||
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-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
|
||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
|
||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
|
||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 h1:2770sDpzrjjsAtVhSeUFseziht227YAWYHLGNM8QPwY=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
|
||||
sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q=
|
||||
sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
@@ -0,0 +1,413 @@
|
||||
package raczylocom
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
v1batch "k8s.io/api/batch/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"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/utils/pointer"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
raczylocomv1 "raczylo.com/kubernetes-images-sync-operator/api/raczylo.com/v1"
|
||||
"raczylo.com/kubernetes-images-sync-operator/shared"
|
||||
)
|
||||
|
||||
type ClusterImageReconciler struct {
|
||||
client.Client
|
||||
Scheme *runtime.Scheme
|
||||
MaxParallelJobs int
|
||||
ActiveJobs int
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=raczylo.com,resources=clusterimages,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=raczylo.com,resources=clusterimages/status,verbs=get;update;patch
|
||||
// +kubebuilder:rbac:groups=raczylo.com,resources=clusterimages/finalizers,verbs=update
|
||||
// # additional RBAC rules - create and manage jobs
|
||||
// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=raczylo.com,resources=clusterimageexports,verbs=get;list;watch;update;patch
|
||||
func (r *ClusterImageReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
l := log.FromContext(ctx)
|
||||
|
||||
clusterImage := &raczylocomv1.ClusterImage{}
|
||||
if err := r.Get(ctx, req.NamespacedName, clusterImage); err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
l.Error(err, "unable to fetch ClusterImage")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
clusterImageExport := &raczylocomv1.ClusterImageExport{}
|
||||
if err := r.Get(ctx, types.NamespacedName{Name: clusterImage.Spec.ExportName, Namespace: clusterImage.Namespace}, clusterImageExport); err != nil {
|
||||
l.Error(err, "unable to fetch ClusterImageExport")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
r.MaxParallelJobs = clusterImageExport.Spec.MaxConcurrentJobs
|
||||
|
||||
// If the ClusterImage is new, set its status to PENDING
|
||||
if clusterImage.Status.Progress == "" {
|
||||
clusterImage.Status.Progress = shared.STATUS_PENDING
|
||||
if err := r.Status().Update(ctx, clusterImage); err != nil {
|
||||
l.Error(err, "unable to update ClusterImage status to PENDING")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{Requeue: true}, nil
|
||||
}
|
||||
|
||||
// If we've reached the maximum number of parallel jobs, requeue
|
||||
if r.ActiveJobs >= r.MaxParallelJobs && clusterImage.Status.Progress == shared.STATUS_PENDING {
|
||||
return ctrl.Result{RequeueAfter: time.Second * 30}, nil
|
||||
}
|
||||
|
||||
// Process the ClusterImage based on its current status
|
||||
switch clusterImage.Status.Progress {
|
||||
case shared.STATUS_PENDING:
|
||||
return r.handlePendingClusterImage(ctx, clusterImage, l)
|
||||
case shared.STATUS_RUNNING, shared.STATUS_RETRYING:
|
||||
return r.handleRunningClusterImage(ctx, clusterImage, l)
|
||||
case shared.STATUS_SUCCESS, shared.STATUS_FAILED, shared.STATUS_PRESENT:
|
||||
return ctrl.Result{}, nil // No further action needed
|
||||
default:
|
||||
l.Info("Unexpected ClusterImage status", "Status", clusterImage.Status.Progress)
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ClusterImageReconciler) handlePendingClusterImage(ctx context.Context, clusterImage *raczylocomv1.ClusterImage, l logr.Logger) (ctrl.Result, error) {
|
||||
// Check if the image is present
|
||||
exists, err := r.checkImageExists(ctx, clusterImage)
|
||||
if err != nil {
|
||||
l.Error(err, "unable to check if image exists")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
if exists {
|
||||
clusterImage.Status.Progress = shared.STATUS_PRESENT
|
||||
if err := r.Status().Update(ctx, clusterImage); err != nil {
|
||||
l.Error(err, "unable to update ClusterImage status")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// Fetch the associated ClusterImageExport
|
||||
clusterImageExport := &raczylocomv1.ClusterImageExport{}
|
||||
if err := r.Get(ctx, types.NamespacedName{Name: clusterImage.Spec.ExportName, Namespace: clusterImage.Namespace}, clusterImageExport); err != nil {
|
||||
l.Error(err, "unable to fetch ClusterImageExport")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Create the backup job
|
||||
if err := r.createBackupJob(ctx, clusterImage, clusterImageExport, l); err != nil {
|
||||
l.Error(err, "unable to create backup job")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Update ClusterImage status to RUNNING
|
||||
clusterImage.Status.Progress = shared.STATUS_RUNNING
|
||||
if err := r.Status().Update(ctx, clusterImage); err != nil {
|
||||
l.Error(err, "unable to update ClusterImage status to RUNNING")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Increment the active jobs count
|
||||
r.ActiveJobs++
|
||||
|
||||
return ctrl.Result{Requeue: true}, nil
|
||||
}
|
||||
|
||||
func (r *ClusterImageReconciler) handleRunningClusterImage(ctx context.Context, clusterImage *raczylocomv1.ClusterImage, l logr.Logger) (ctrl.Result, error) {
|
||||
// Check for existing job for this ClusterImage
|
||||
existingJob := &v1batch.Job{}
|
||||
jobName := fmt.Sprintf("img-export-%s", clusterImage.Name)
|
||||
err := r.Get(ctx, types.NamespacedName{Name: jobName, Namespace: clusterImage.Namespace}, existingJob)
|
||||
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
// Job doesn't exist, set status back to PENDING
|
||||
clusterImage.Status.Progress = shared.STATUS_PENDING
|
||||
if err := r.Status().Update(ctx, clusterImage); err != nil {
|
||||
l.Error(err, "unable to update ClusterImage status back to PENDING")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{Requeue: true}, nil
|
||||
}
|
||||
l.Error(err, "unable to check for existing job")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Check job status and update ClusterImage accordingly
|
||||
if existingJob.Status.Succeeded > 0 {
|
||||
clusterImage.Status.Progress = shared.STATUS_SUCCESS
|
||||
r.ActiveJobs--
|
||||
} else if existingJob.Status.Failed > 0 {
|
||||
if clusterImage.Status.RetryCount < 3 {
|
||||
clusterImage.Status.Progress = shared.STATUS_RETRYING
|
||||
clusterImage.Status.RetryCount++
|
||||
if err := r.Delete(ctx, existingJob); err != nil {
|
||||
l.Error(err, "unable to delete failed job for retry")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
r.ActiveJobs--
|
||||
return ctrl.Result{Requeue: true}, nil
|
||||
} else {
|
||||
clusterImage.Status.Progress = shared.STATUS_FAILED
|
||||
r.ActiveJobs--
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.handleJobRestarts(ctx, existingJob, clusterImage); err != nil {
|
||||
l.Error(err, "unable to handle job restarts")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Update ClusterImage status
|
||||
if err := r.Status().Update(ctx, clusterImage); err != nil {
|
||||
l.Error(err, "unable to update ClusterImage status")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Delete the completed job
|
||||
if clusterImage.Status.Progress == shared.STATUS_SUCCESS || clusterImage.Status.Progress == shared.STATUS_FAILED {
|
||||
if err := r.Delete(ctx, existingJob); err != nil && !errors.IsNotFound(err) {
|
||||
l.Error(err, "unable to delete completed job")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
l.Info("Reconciling ClusterImage completed", "Name", clusterImage.Name, "Status", clusterImage.Status.Progress)
|
||||
|
||||
return r.updateClusterImageExportStatus(ctx, clusterImage)
|
||||
}
|
||||
|
||||
func (r *ClusterImageReconciler) cleanupJobPods(ctx context.Context, job *v1batch.Job) error {
|
||||
podList := &v1.PodList{}
|
||||
if err := r.List(ctx, podList, client.InNamespace(job.Namespace), client.MatchingLabels(job.Spec.Selector.MatchLabels)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, pod := range podList.Items {
|
||||
if err := r.Delete(ctx, &pod); err != nil && !errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ClusterImageReconciler) createBackupJob(ctx context.Context, clusterImage *raczylocomv1.ClusterImage, clusterImageExport *raczylocomv1.ClusterImageExport, l logr.Logger) error {
|
||||
normalisedImageName := shared.NormalizeImageName(clusterImage.Spec.FullName)
|
||||
|
||||
defaultCommands := []string{
|
||||
"podman pull " + clusterImage.Spec.FullName,
|
||||
"podman save --quiet -o /tmp/" + normalisedImageName + ".tar " + clusterImage.Spec.FullName,
|
||||
}
|
||||
|
||||
if clusterImage.Spec.Storage == shared.STORAGE_S3 {
|
||||
s3Params := shared.SetupS3Params(clusterImageExport.Spec.Storage.S3)
|
||||
additionalCommands := []string{
|
||||
"./export.py " + strings.Join(s3Params, " ") + " '/tmp/" + normalisedImageName + ".tar' " + "'s3://" + clusterImageExport.Spec.Storage.S3.Bucket + clusterImage.Spec.ExportPath + "/" + clusterImage.Spec.ExportName + "/" + normalisedImageName + ".tar'",
|
||||
}
|
||||
defaultCommands = append(defaultCommands, additionalCommands...)
|
||||
} else if clusterImage.Spec.Storage == shared.STORAGE_FILE {
|
||||
additionalCommands := []string{
|
||||
"./export.py /tmp/" + normalisedImageName + ".tar" + " " + clusterImage.Spec.ExportPath + "/" + clusterImage.Spec.ExportName + "/" + normalisedImageName + ".tar",
|
||||
}
|
||||
defaultCommands = append(defaultCommands, additionalCommands...)
|
||||
}
|
||||
defaultCommands = append(defaultCommands, "rm -f /tmp/"+normalisedImageName+".tar")
|
||||
|
||||
jobParams := shared.JobParams{
|
||||
Name: fmt.Sprintf("img-export-%s", clusterImage.Name),
|
||||
Namespace: clusterImage.Namespace,
|
||||
Image: shared.BACKUP_JOB_IMAGE,
|
||||
Commands: defaultCommands,
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: clusterImage.APIVersion,
|
||||
Kind: clusterImage.Kind,
|
||||
Name: clusterImage.Name,
|
||||
UID: clusterImage.UID,
|
||||
BlockOwnerDeletion: pointer.Bool(true),
|
||||
Controller: pointer.Bool(true),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
backupJob := shared.CreateJob(jobParams, func(raczylocomv1.ClusterImageExport) []string { return nil })
|
||||
|
||||
if err := r.Create(ctx, backupJob); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clusterImage.Status.Progress = shared.STATUS_RUNNING
|
||||
return r.Status().Update(ctx, clusterImage)
|
||||
}
|
||||
|
||||
func (r *ClusterImageReconciler) updateClusterImageExportStatus(ctx context.Context, clusterImage *raczylocomv1.ClusterImage) (ctrl.Result, error) {
|
||||
l := log.FromContext(ctx)
|
||||
|
||||
clusterImageExport := &raczylocomv1.ClusterImageExport{}
|
||||
if err := r.Get(ctx, types.NamespacedName{Name: clusterImage.Spec.ExportName, Namespace: clusterImage.Namespace}, clusterImageExport); err != nil {
|
||||
l.Error(err, "unable to fetch ClusterImageExport")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
clusterImageList := &raczylocomv1.ClusterImageList{}
|
||||
if err := r.List(ctx, clusterImageList, client.InNamespace(clusterImage.Namespace), client.MatchingFields{"spec.exportName": clusterImage.Spec.ExportName}); err != nil {
|
||||
l.Error(err, "unable to list ClusterImages")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
allCompleted := true
|
||||
anyFailed := false
|
||||
anyRunning := false
|
||||
|
||||
for _, ci := range clusterImageList.Items {
|
||||
switch ci.Status.Progress {
|
||||
case shared.STATUS_SUCCESS, shared.STATUS_PRESENT:
|
||||
// These statuses are considered completed
|
||||
case shared.STATUS_FAILED:
|
||||
anyFailed = true
|
||||
allCompleted = false
|
||||
case shared.STATUS_RUNNING, shared.STATUS_RETRYING:
|
||||
allCompleted = false
|
||||
anyRunning = true
|
||||
case shared.STATUS_PENDING:
|
||||
allCompleted = false
|
||||
}
|
||||
}
|
||||
|
||||
var newStatus string
|
||||
if allCompleted {
|
||||
newStatus = shared.STATUS_SUCCESS
|
||||
} else if anyFailed {
|
||||
newStatus = shared.STATUS_FAILED
|
||||
} else if anyRunning {
|
||||
newStatus = shared.STATUS_RUNNING
|
||||
} else {
|
||||
newStatus = shared.STATUS_PENDING
|
||||
}
|
||||
|
||||
if clusterImageExport.Status.Progress != newStatus {
|
||||
clusterImageExport.Status.Progress = newStatus
|
||||
if err := r.Status().Update(ctx, clusterImageExport); err != nil {
|
||||
l.Error(err, "unable to update ClusterImageExport status")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
l.Info("Updated ClusterImageExport status", "ExportName", clusterImageExport.Name, "NewStatus", newStatus)
|
||||
}
|
||||
|
||||
// If there are still pending or running images, requeue
|
||||
if !allCompleted {
|
||||
return ctrl.Result{Requeue: true}, nil
|
||||
}
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
func (r *ClusterImageReconciler) handleJobRestarts(ctx context.Context, job *v1batch.Job, clusterImage *raczylocomv1.ClusterImage) error {
|
||||
podList := &v1.PodList{}
|
||||
if err := r.List(ctx, podList, client.InNamespace(job.Namespace), client.MatchingLabels(job.Spec.Selector.MatchLabels)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, pod := range podList.Items {
|
||||
for _, containerStatus := range pod.Status.ContainerStatuses {
|
||||
if containerStatus.RestartCount > 0 {
|
||||
clusterImage.Status.RetryCount += int(containerStatus.RestartCount)
|
||||
if clusterImage.Status.RetryCount >= 3 {
|
||||
clusterImage.Status.Progress = shared.STATUS_FAILED
|
||||
if err := r.Status().Update(ctx, clusterImage); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.removeAllJobsAndContainers(ctx, clusterImage.Namespace)
|
||||
} else {
|
||||
clusterImage.Status.Progress = shared.STATUS_RETRYING
|
||||
}
|
||||
|
||||
if err := r.Status().Update(ctx, clusterImage); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupWithManager sets up the controller with the Manager.
|
||||
func (r *ClusterImageReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&raczylocomv1.ClusterImage{}).
|
||||
Owns(&v1batch.Job{}).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
func (r *ClusterImageReconciler) removeAllJobsAndContainers(ctx context.Context, namespace string) error {
|
||||
jobList := &v1batch.JobList{}
|
||||
if err := r.List(ctx, jobList, client.InNamespace(namespace), client.MatchingLabels{"app": "image-export"}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, job := range jobList.Items {
|
||||
if err := r.Delete(ctx, &job, client.PropagationPolicy(metav1.DeletePropagationForeground)); err != nil && !errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ClusterImageReconciler) checkImageExists(ctx context.Context, clusterImage *raczylocomv1.ClusterImage) (bool, error) {
|
||||
clusterImageList := &raczylocomv1.ClusterImageList{}
|
||||
if err := r.List(ctx, clusterImageList); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, ci := range clusterImageList.Items {
|
||||
if ci.Spec.FullName == clusterImage.Spec.FullName && ci.Name != clusterImage.Name {
|
||||
if ci.Status.Progress == shared.STATUS_SUCCESS || ci.Status.Progress == shared.STATUS_PRESENT || ci.Status.Progress == shared.STATUS_RUNNING {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the image is already in the COMPLETED state
|
||||
if clusterImage.Status.Progress == shared.STATUS_SUCCESS {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (r *ClusterImageReconciler) isJobStarted(ctx context.Context, job *v1batch.Job) (bool, error) {
|
||||
podList := &v1.PodList{}
|
||||
if err := r.List(ctx, podList, client.InNamespace(job.Namespace), client.MatchingLabels(job.Spec.Selector.MatchLabels)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, pod := range podList.Items {
|
||||
if pod.Status.Phase == v1.PodRunning {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (r *ClusterImageReconciler) hasJobTimedOut(job *v1batch.Job) bool {
|
||||
// Check if the job has been running for more than 5 minutes without starting
|
||||
return time.Since(job.CreationTimestamp.Time) > 5*time.Minute
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
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 raczylocom
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
raczylocomv1 "raczylo.com/kubernetes-images-sync-operator/api/raczylo.com/v1"
|
||||
)
|
||||
|
||||
var _ = Describe("ClusterImage 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
|
||||
}
|
||||
clusterimage := &raczylocomv1.ClusterImage{}
|
||||
|
||||
BeforeEach(func() {
|
||||
By("creating the custom resource for the Kind ClusterImage")
|
||||
err := k8sClient.Get(ctx, typeNamespacedName, clusterimage)
|
||||
if err != nil && errors.IsNotFound(err) {
|
||||
resource := &raczylocomv1.ClusterImage{
|
||||
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 := &raczylocomv1.ClusterImage{}
|
||||
err := k8sClient.Get(ctx, typeNamespacedName, resource)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Cleanup the specific resource instance ClusterImage")
|
||||
Expect(k8sClient.Delete(ctx, resource)).To(Succeed())
|
||||
})
|
||||
It("should successfully reconcile the resource", func() {
|
||||
By("Reconciling the created resource")
|
||||
controllerReconciler := &ClusterImageReconciler{
|
||||
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.
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,320 @@
|
||||
package raczylocom
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
v1batch "k8s.io/api/batch/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/utils/pointer"
|
||||
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"
|
||||
|
||||
raczylocomv1 "raczylo.com/kubernetes-images-sync-operator/api/raczylo.com/v1"
|
||||
shared "raczylo.com/kubernetes-images-sync-operator/shared"
|
||||
)
|
||||
|
||||
// ClusterImageExportReconciler reconciles a ClusterImageExport object
|
||||
type ClusterImageExportReconciler struct {
|
||||
client.Client
|
||||
Scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=raczylo.com,resources=clusterimageexports,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=raczylo.com,resources=clusterimageexports/status,verbs=get;update;patch
|
||||
// +kubebuilder:rbac:groups=raczylo.com,resources=clusterimageexports/finalizers,verbs=update
|
||||
// additional RBAC rules
|
||||
// +kubebuilder:rbac:groups=raczylo.com,resources=clusterimages,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch
|
||||
// +kubebuilder:rbac:groups=apps,resources=daemonsets,verbs=get;list;watch
|
||||
// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=batch,resources=cronjobs,verbs=get;list;watch
|
||||
|
||||
const clusterImageExportFinalizer = "finalizer.clusterimageexport.raczylo.com"
|
||||
|
||||
func (r *ClusterImageExportReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
l := log.FromContext(ctx)
|
||||
l.Info("Reconciling ClusterImageExport")
|
||||
|
||||
// Fetch the ClusterImageExport instance
|
||||
clusterImageExport := &raczylocomv1.ClusterImageExport{}
|
||||
if err := r.Get(ctx, req.NamespacedName, clusterImageExport); err != nil {
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
|
||||
if !clusterImageExport.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||
return r.handleDeletion(ctx, clusterImageExport)
|
||||
}
|
||||
|
||||
// Add finalizer if it doesn't exist
|
||||
if !controllerutil.ContainsFinalizer(clusterImageExport, clusterImageExportFinalizer) {
|
||||
controllerutil.AddFinalizer(clusterImageExport, clusterImageExportFinalizer)
|
||||
if err := r.Update(ctx, clusterImageExport); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// Early return if the ClusterImageExport is already in a completed state
|
||||
if clusterImageExport.Status.Progress == shared.STATUS_SUCCESS || clusterImageExport.Status.Progress == shared.STATUS_FAILED {
|
||||
l.Info("ClusterImageExport is already in a completed state", "Status", clusterImageExport.Status.Progress)
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// If the status is empty, set it to PENDING
|
||||
if clusterImageExport.Status.Progress == "" {
|
||||
clusterImageExport.Status.Progress = shared.STATUS_PENDING
|
||||
if err := r.Status().Update(ctx, clusterImageExport); err != nil {
|
||||
l.Error(err, "unable to update ClusterImageExport status")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// Proceed with the rest of the reconciliation logic
|
||||
fullImagesList, err := r.listImagesInCluster(ctx, l, clusterImageExport)
|
||||
if err != nil {
|
||||
l.Error(err, "unable to list images in the cluster")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
clusterImageExport.Status.Progress = shared.STATUS_RUNNING
|
||||
if err := r.Status().Update(ctx, clusterImageExport); err != nil {
|
||||
l.Error(err, "unable to update ClusterImageExport status to RUNNING")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
for _, image := range fullImagesList.Containers {
|
||||
nameHash := fmt.Sprintf("%x", md5.Sum([]byte(clusterImageExport.Name+image.Image+image.Tag+image.Sha)))[:14]
|
||||
|
||||
// Check if the ClusterImage already exists
|
||||
clusterImage := &raczylocomv1.ClusterImage{}
|
||||
err := r.Get(ctx, client.ObjectKey{Namespace: clusterImageExport.Namespace, Name: nameHash}, clusterImage)
|
||||
if err == nil {
|
||||
// ClusterImage exists, check its status
|
||||
if clusterImage.Status.Progress == shared.STATUS_FAILED {
|
||||
clusterImageExport.Status.Progress = shared.STATUS_FAILED
|
||||
if err := r.Status().Update(ctx, clusterImageExport); err != nil {
|
||||
l.Error(err, "unable to update ClusterImageExport status to FAILED")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
continue
|
||||
} else if !errors.IsNotFound(err) {
|
||||
l.Error(err, "unable to get ClusterImage")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Create a new ClusterImage
|
||||
newClusterImage := &raczylocomv1.ClusterImage{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: nameHash,
|
||||
Namespace: clusterImageExport.Namespace,
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: clusterImageExport.APIVersion,
|
||||
Kind: clusterImageExport.Kind,
|
||||
Name: clusterImageExport.Name,
|
||||
UID: clusterImageExport.UID,
|
||||
Controller: pointer.Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: raczylocomv1.ClusterImageSpec{
|
||||
Image: image.Image,
|
||||
Tag: image.Tag,
|
||||
Sha: image.Sha,
|
||||
FullName: image.FullName,
|
||||
Storage: clusterImageExport.Spec.Storage.StorageTarget,
|
||||
ExportName: clusterImageExport.Name,
|
||||
ExportPath: clusterImageExport.Spec.BasePath,
|
||||
},
|
||||
}
|
||||
|
||||
if err := r.Create(ctx, newClusterImage); err != nil {
|
||||
l.Error(err, "unable to create ClusterImage", "image", image)
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// Check if all ClusterImages are completed
|
||||
allCompleted, err := r.checkAllClusterImagesCompleted(ctx, clusterImageExport)
|
||||
if err != nil {
|
||||
l.Error(err, "unable to check ClusterImages status")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
if allCompleted {
|
||||
clusterImageExport.Status.Progress = shared.STATUS_SUCCESS
|
||||
if err := r.Status().Update(ctx, clusterImageExport); err != nil {
|
||||
l.Error(err, "unable to update ClusterImageExport status to SUCCESS")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return ctrl.Result{Requeue: !allCompleted}, nil
|
||||
}
|
||||
|
||||
func (r *ClusterImageExportReconciler) checkAllClusterImagesCompleted(ctx context.Context, clusterImageExport *raczylocomv1.ClusterImageExport) (bool, error) {
|
||||
clusterImageList := &raczylocomv1.ClusterImageList{}
|
||||
if err := r.List(ctx, clusterImageList, client.InNamespace(clusterImageExport.Namespace), client.MatchingFields{"spec.exportName": clusterImageExport.Name}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, ci := range clusterImageList.Items {
|
||||
if ci.Status.Progress != shared.STATUS_SUCCESS && ci.Status.Progress != shared.STATUS_PRESENT {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// SetupWithManager sets up the controller with the Manager.
|
||||
|
||||
func (r *ClusterImageExportReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&raczylocomv1.ClusterImageExport{}).
|
||||
Owns(&raczylocomv1.ClusterImage{}).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
func (r *ClusterImageExportReconciler) listImagesInCluster(ctx context.Context, l logr.Logger, clusterImageExport *raczylocomv1.ClusterImageExport) (shared.ContainersList, error) {
|
||||
containersList := shared.ContainersList{}
|
||||
if err := shared.ListAndProcessResources[*shared.DeploymentWrapper](ctx, r.Client, &appsv1.DeploymentList{}, &containersList); err != nil {
|
||||
return shared.ContainersList{}, err
|
||||
}
|
||||
if err := shared.ListAndProcessResources[*shared.JobWrapper](ctx, r.Client, &batchv1.JobList{}, &containersList); err != nil {
|
||||
return shared.ContainersList{}, err
|
||||
}
|
||||
if err := shared.ListAndProcessResources[*shared.DaemonSetWrapper](ctx, r.Client, &appsv1.DaemonSetList{}, &containersList); err != nil {
|
||||
return shared.ContainersList{}, err
|
||||
}
|
||||
if err := shared.ListAndProcessResources[*shared.CronJobWrapper](ctx, r.Client, &batchv1.CronJobList{}, &containersList); err != nil {
|
||||
return shared.ContainersList{}, err
|
||||
}
|
||||
|
||||
if len(clusterImageExport.Spec.Includes) > 0 {
|
||||
containersList = shared.IncludeOnlyImages(containersList, clusterImageExport.Spec.Includes)
|
||||
}
|
||||
|
||||
if len(clusterImageExport.Spec.Excludes) > 0 {
|
||||
containersList = shared.RemoveExcludedImages(containersList, clusterImageExport.Spec.Excludes)
|
||||
}
|
||||
|
||||
containersList = shared.RemoveDuplicates(containersList)
|
||||
l.Info("List of containers in the cluster", "containers", containersList)
|
||||
|
||||
return containersList, nil
|
||||
}
|
||||
|
||||
func (r *ClusterImageExportReconciler) handleDeletion(ctx context.Context, clusterImageExport *raczylocomv1.ClusterImageExport) (ctrl.Result, error) {
|
||||
l := log.FromContext(ctx)
|
||||
|
||||
if controllerutil.ContainsFinalizer(clusterImageExport, clusterImageExportFinalizer) {
|
||||
// Run the cleanup job
|
||||
if err := r.runCleanupJob(ctx, clusterImageExport); err != nil {
|
||||
l.Error(err, "Failed to run cleanup job")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Remove the finalizer
|
||||
controllerutil.RemoveFinalizer(clusterImageExport, clusterImageExportFinalizer)
|
||||
if err := r.Update(ctx, clusterImageExport); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
func (r *ClusterImageExportReconciler) runCleanupJob(ctx context.Context, clusterImageExport *raczylocomv1.ClusterImageExport) error {
|
||||
l := log.FromContext(ctx)
|
||||
|
||||
normalisedImageName := "cleanup-" + shared.NormalizeImageName(clusterImageExport.Name)
|
||||
|
||||
defaultCommands := []string{}
|
||||
|
||||
if clusterImageExport.Spec.Storage.StorageTarget == shared.STORAGE_S3 {
|
||||
s3Params := shared.SetupS3Params(clusterImageExport.Spec.Storage.S3)
|
||||
additionalCommands := []string{
|
||||
"./cleanup.py " + strings.Join(s3Params, " ") + " 's3://" + clusterImageExport.Spec.Storage.S3.Bucket + clusterImageExport.Spec.BasePath + "/" + clusterImageExport.ObjectMeta.Name + "/'",
|
||||
}
|
||||
defaultCommands = append(defaultCommands, additionalCommands...)
|
||||
} else if clusterImageExport.Spec.Storage.StorageTarget == shared.STORAGE_FILE {
|
||||
additionalCommands := []string{
|
||||
"./cleanup.py" + "'" + clusterImageExport.Spec.BasePath + "/" + clusterImageExport.ObjectMeta.Name + "/'",
|
||||
}
|
||||
defaultCommands = append(defaultCommands, additionalCommands...)
|
||||
}
|
||||
|
||||
jobParams := shared.JobParams{
|
||||
Name: normalisedImageName,
|
||||
Namespace: clusterImageExport.Namespace,
|
||||
Image: shared.BACKUP_JOB_IMAGE,
|
||||
Commands: defaultCommands,
|
||||
}
|
||||
|
||||
cleanupJob := shared.CreateJob(jobParams, func(raczylocomv1.ClusterImageExport) []string { return nil })
|
||||
|
||||
if err := r.Create(ctx, cleanupJob); err != nil {
|
||||
l.Error(err, "Failed to create cleanup job")
|
||||
return err
|
||||
}
|
||||
|
||||
l.Info("Created cleanup job")
|
||||
|
||||
go func() {
|
||||
if err := r.waitForJobCompletionAndDelete(ctx, cleanupJob); err != nil {
|
||||
l.Error(err, "Failed to wait for job completion and delete")
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ClusterImageExportReconciler) waitForJobCompletionAndDelete(ctx context.Context, job *v1batch.Job) error {
|
||||
l := log.FromContext(ctx)
|
||||
key := client.ObjectKeyFromObject(job)
|
||||
|
||||
// Wait for the job to complete
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
if err := r.Get(ctx, key, job); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if job.Status.Succeeded > 0 {
|
||||
// Job completed successfully, delete it
|
||||
l.Info("Cleanup job completed, deleting", "job", job.Name)
|
||||
if err := r.Delete(ctx, job, client.PropagationPolicy(metav1.DeletePropagationBackground)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if job.Status.Failed > 0 {
|
||||
// Job failed, log the error but still delete the job
|
||||
l.Error(nil, "Cleanup job failed", "job", job.Name)
|
||||
if err := r.Delete(ctx, job, client.PropagationPolicy(metav1.DeletePropagationBackground)); err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("cleanup job failed: %s", job.Name)
|
||||
}
|
||||
|
||||
// Job still running, wait and check again
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
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 raczylocom
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
raczylocomv1 "raczylo.com/kubernetes-images-sync-operator/api/raczylo.com/v1"
|
||||
)
|
||||
|
||||
var _ = Describe("ClusterImageExport 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
|
||||
}
|
||||
clusterimageexport := &raczylocomv1.ClusterImageExport{}
|
||||
|
||||
BeforeEach(func() {
|
||||
By("creating the custom resource for the Kind ClusterImageExport")
|
||||
err := k8sClient.Get(ctx, typeNamespacedName, clusterimageexport)
|
||||
if err != nil && errors.IsNotFound(err) {
|
||||
resource := &raczylocomv1.ClusterImageExport{
|
||||
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 := &raczylocomv1.ClusterImageExport{}
|
||||
err := k8sClient.Get(ctx, typeNamespacedName, resource)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Cleanup the specific resource instance ClusterImageExport")
|
||||
Expect(k8sClient.Delete(ctx, resource)).To(Succeed())
|
||||
})
|
||||
It("should successfully reconcile the resource", func() {
|
||||
By("Reconciling the created resource")
|
||||
controllerReconciler := &ClusterImageExportReconciler{
|
||||
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.
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
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 raczylocom
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
. "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"
|
||||
|
||||
raczylocomv1 "raczylo.com/kubernetes-images-sync-operator/api/raczylo.com/v1"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
// 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
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
var ctx context.Context
|
||||
var cancel context.CancelFunc
|
||||
|
||||
func TestControllers(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
|
||||
RunSpecs(t, "Controller Suite")
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func() {
|
||||
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
|
||||
|
||||
ctx, cancel = context.WithCancel(context.TODO())
|
||||
|
||||
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.31.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 = raczylocomv1.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")
|
||||
cancel()
|
||||
err := testEnv.Stop()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
@@ -0,0 +1,5 @@
|
||||
version: 1
|
||||
force:
|
||||
minor: 1
|
||||
existing: false
|
||||
strict: false
|
||||
@@ -0,0 +1,98 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// JOB IMAGES
|
||||
BACKUP_JOB_IMAGE = "ghcr.io/lukaszraczylo/docker-image-management:v0.0.6"
|
||||
|
||||
// AVAILABLE STATUSES
|
||||
STATUS_PENDING = "PENDING"
|
||||
STATUS_STARTING = "STARTING"
|
||||
STATUS_RETRYING = "RETRYING"
|
||||
STATUS_RUNNING = "RUNNING"
|
||||
STATUS_FAILED = "FAILED"
|
||||
STATUS_SUCCESS = "COMPLETED"
|
||||
STATUS_PRESENT = "PRESENT"
|
||||
|
||||
// STORAGE DEFINITIONS
|
||||
STORAGE_S3 = "S3"
|
||||
STORAGE_FILE = "FILE"
|
||||
)
|
||||
|
||||
type Container struct {
|
||||
Image string `json:"image"`
|
||||
Tag string `json:"tag"`
|
||||
Sha string `json:"sha"`
|
||||
FullName string `json:"fullName"`
|
||||
}
|
||||
|
||||
type ContainersList struct {
|
||||
Containers []Container `json:"containers"`
|
||||
}
|
||||
|
||||
func RemoveDuplicates(containersList ContainersList) ContainersList {
|
||||
// remove duplicates from the list
|
||||
encountered := map[Container]bool{}
|
||||
result := ContainersList{}
|
||||
for v := range containersList.Containers {
|
||||
if !encountered[containersList.Containers[v]] {
|
||||
encountered[containersList.Containers[v]] = true
|
||||
result.Containers = append(result.Containers, containersList.Containers[v])
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func RemoveExcludedImages(containers ContainersList, excludes []string) ContainersList {
|
||||
// remove excluded images from the list
|
||||
result := ContainersList{}
|
||||
for _, container := range containers.Containers {
|
||||
excluded := false
|
||||
for _, exclude := range excludes {
|
||||
if strings.Contains(strings.ToLower(container.Image), strings.ToLower(exclude)) {
|
||||
excluded = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !excluded {
|
||||
result.Containers = append(result.Containers, container)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func IncludeOnlyImages(containers ContainersList, includes []string) ContainersList {
|
||||
// include only images from the list
|
||||
result := ContainersList{}
|
||||
for _, container := range containers.Containers {
|
||||
included := false
|
||||
for _, include := range includes {
|
||||
if strings.Contains(strings.ToLower(container.Image), strings.ToLower(include)) {
|
||||
included = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if included {
|
||||
result.Containers = append(result.Containers, container)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
var imageNameRegexp = regexp.MustCompile(`[/:@&=+$,\?%\{\}\[\]\\^~#\s]`)
|
||||
var imageNameRegexpReplace = regexp.MustCompile(`-+`)
|
||||
|
||||
func NormalizeImageName(name string) string {
|
||||
// Replace special characters with hyphens
|
||||
normalized := imageNameRegexp.ReplaceAllString(name, "-")
|
||||
|
||||
// Remove consecutive hyphens
|
||||
normalized = imageNameRegexpReplace.ReplaceAllString(normalized, "-")
|
||||
|
||||
// Trim leading and trailing hyphens
|
||||
return strings.Trim(normalized, "-")
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/pointer"
|
||||
raczylocomv1 "raczylo.com/kubernetes-images-sync-operator/api/raczylo.com/v1"
|
||||
)
|
||||
|
||||
type JobParams struct {
|
||||
Name string
|
||||
Namespace string
|
||||
Image string
|
||||
Commands []string
|
||||
EnvVars []corev1.EnvVar
|
||||
OwnerReferences []metav1.OwnerReference
|
||||
}
|
||||
|
||||
func CreateJob[T any](params JobParams, setupFunc func(T) []string) *batchv1.Job {
|
||||
return &batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: params.Name,
|
||||
Namespace: params.Namespace,
|
||||
OwnerReferences: params.OwnerReferences,
|
||||
Labels: map[string]string{
|
||||
"app": "image-export",
|
||||
},
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"app": "image-export",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
RestartPolicy: corev1.RestartPolicyOnFailure,
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "export",
|
||||
Image: params.Image,
|
||||
TTY: true,
|
||||
Command: []string{
|
||||
"bash",
|
||||
"-c",
|
||||
strings.Join(params.Commands, " && "),
|
||||
},
|
||||
Env: params.EnvVars,
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Privileged: pointer.Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func SetupS3Params(s3Config raczylocomv1.ClusterImageStorageS3) []string {
|
||||
params := []string{}
|
||||
if s3Config.UseRole {
|
||||
params = append(params, "--use-role")
|
||||
} else {
|
||||
params = append(params, fmt.Sprintf("--aws_access_key_id='%s'", s3Config.AccessKey))
|
||||
params = append(params, fmt.Sprintf("--aws_secret_access_key='%s'", s3Config.SecretKey))
|
||||
}
|
||||
if s3Config.RoleARN != "" {
|
||||
params = append(params, fmt.Sprintf("--role_name='%s'", s3Config.RoleARN))
|
||||
}
|
||||
if s3Config.Endpoint != "" {
|
||||
params = append(params, fmt.Sprintf("--endpoint_url='%s'", s3Config.Endpoint))
|
||||
}
|
||||
if s3Config.Region != "" {
|
||||
params = append(params, fmt.Sprintf("--region=%s", s3Config.Region))
|
||||
}
|
||||
return params
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
raczylocomv1 "raczylo.com/kubernetes-images-sync-operator/api/raczylo.com/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
)
|
||||
|
||||
type K8sResource interface {
|
||||
GetPodSpec() *corev1.PodSpec
|
||||
}
|
||||
|
||||
// Wrapper types
|
||||
type DeploymentWrapper appsv1.Deployment
|
||||
type JobWrapper batchv1.Job
|
||||
type DaemonSetWrapper appsv1.DaemonSet
|
||||
type CronJobWrapper batchv1.CronJob
|
||||
|
||||
// Implement the K8sResource interface for wrapper types
|
||||
func (d *DeploymentWrapper) GetPodSpec() *corev1.PodSpec { return &d.Spec.Template.Spec }
|
||||
func (j *JobWrapper) GetPodSpec() *corev1.PodSpec { return &j.Spec.Template.Spec }
|
||||
func (ds *DaemonSetWrapper) GetPodSpec() *corev1.PodSpec { return &ds.Spec.Template.Spec }
|
||||
func (cj *CronJobWrapper) GetPodSpec() *corev1.PodSpec {
|
||||
return &cj.Spec.JobTemplate.Spec.Template.Spec
|
||||
}
|
||||
|
||||
func processContainerName(containerName string) (Container, error) {
|
||||
cnt := Container{}
|
||||
parts := strings.Split(containerName, "@")
|
||||
if len(parts) > 2 {
|
||||
return cnt, fmt.Errorf("invalid container name format: %s", containerName)
|
||||
}
|
||||
imageAndTag := strings.Split(parts[0], ":")
|
||||
cnt.Image = imageAndTag[0]
|
||||
if len(imageAndTag) > 2 {
|
||||
return cnt, fmt.Errorf("invalid image:tag format: %s", parts[0])
|
||||
}
|
||||
if len(imageAndTag) == 2 {
|
||||
cnt.Tag = imageAndTag[1]
|
||||
}
|
||||
if len(parts) == 2 {
|
||||
shaParts := strings.SplitN(parts[1], ":", 2)
|
||||
if len(shaParts) != 2 || (shaParts[0] != "sha" && shaParts[0] != "sha256") {
|
||||
return cnt, fmt.Errorf("invalid SHA format: %s", parts[1])
|
||||
}
|
||||
cnt.Sha = parts[1]
|
||||
}
|
||||
cnt.FullName = containerName
|
||||
// if tag is empty and sha is empty - use tag 'latest'
|
||||
if cnt.Sha == "" && cnt.Tag == "" {
|
||||
cnt.Tag = "latest"
|
||||
}
|
||||
|
||||
if cnt.Image == "" {
|
||||
return cnt, fmt.Errorf("image name is required")
|
||||
}
|
||||
return cnt, nil
|
||||
}
|
||||
|
||||
func processContainers[T K8sResource](resource T, containersList *ContainersList) error {
|
||||
podSpec := resource.GetPodSpec()
|
||||
if podSpec == nil {
|
||||
return fmt.Errorf("nil PodSpec")
|
||||
}
|
||||
|
||||
allContainers := append(podSpec.Containers, podSpec.InitContainers...)
|
||||
for _, container := range allContainers {
|
||||
if err := processContainer(container.Image, containersList); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, container := range podSpec.EphemeralContainers {
|
||||
if err := processContainer(container.EphemeralContainerCommon.Image, containersList); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// processContainer handles the processing of a single container image
|
||||
func processContainer(image string, containersList *ContainersList) error {
|
||||
cnt, err := processContainerName(image)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to process container name: %s - %w", image, err)
|
||||
}
|
||||
containersList.Containers = append(containersList.Containers, cnt)
|
||||
return nil
|
||||
}
|
||||
|
||||
// listAndProcessResources is a generic function to list and process K8s resources
|
||||
func ListAndProcessResources[T K8sResource, L client.ObjectList](ctx context.Context, r client.Client, list L, containersList *ContainersList) error {
|
||||
if err := r.List(ctx, list, &client.ListOptions{}); err != nil {
|
||||
return fmt.Errorf("failed to list resources: %w", err)
|
||||
}
|
||||
|
||||
switch typedList := any(list).(type) {
|
||||
case *appsv1.DeploymentList:
|
||||
for i := range typedList.Items {
|
||||
if err := processContainers((*DeploymentWrapper)(&typedList.Items[i]), containersList); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case *batchv1.JobList:
|
||||
for i := range typedList.Items {
|
||||
if err := processContainers((*JobWrapper)(&typedList.Items[i]), containersList); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case *appsv1.DaemonSetList:
|
||||
for i := range typedList.Items {
|
||||
if err := processContainers((*DaemonSetWrapper)(&typedList.Items[i]), containersList); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case *batchv1.CronJobList:
|
||||
for i := range typedList.Items {
|
||||
if err := processContainers((*CronJobWrapper)(&typedList.Items[i]), containersList); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported list type: %T", list)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetupIndexers(mgr manager.Manager) error {
|
||||
return mgr.GetFieldIndexer().IndexField(context.Background(), &raczylocomv1.ClusterImage{}, "spec.exportName", func(rawObj client.Object) []string {
|
||||
clusterImage := rawObj.(*raczylocomv1.ClusterImage)
|
||||
return []string{clusterImage.Spec.ExportName}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
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 kubernetes-images-sync-operator suite\n")
|
||||
RunSpecs(t, "e2e suite")
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
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/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"raczylo.com/kubernetes-images-sync-operator/test/utils"
|
||||
)
|
||||
|
||||
const namespace = "kubernetes-images-sync-operator-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
|
||||
var projectimage = "example.com/kubernetes-images-sync-operator:v0.0.1"
|
||||
|
||||
By("building the manager(Operator) image")
|
||||
cmd := exec.Command("make", "docker-build", fmt.Sprintf("IMG=%s", 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 cm-raczylo-com")
|
||||
cmd = exec.Command("make", "deploy", fmt.Sprintf("IMG=%s", projectimage))
|
||||
_, err = utils.Run(cmd)
|
||||
ExpectWithOffset(1, err).NotTo(HaveOccurred())
|
||||
|
||||
By("validating that the cm-raczylo-com pod is running as expected")
|
||||
verifyControllerUp := func() error {
|
||||
// Get pod name
|
||||
|
||||
cmd = exec.Command("kubectl", "get",
|
||||
"pods", "-l", "control-plane=cm-raczylo-com",
|
||||
"-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("cm-raczylo-com"))
|
||||
|
||||
// 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())
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2" //nolint:golint,revive
|
||||
)
|
||||
|
||||
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(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(GinkgoWriter, "chdir dir: %s\n", err)
|
||||
}
|
||||
|
||||
cmd.Env = append(os.Environ(), "GO111MODULE=on")
|
||||
command := strings.Join(cmd.Args, " ")
|
||||
_, _ = fmt.Fprintf(GinkgoWriter, "running: %s\n", command)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return output, fmt.Errorf("%s failed with error: (%v) %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.Replace(wd, "/test/e2e", "", -1)
|
||||
return wd, nil
|
||||
}
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
find chart/ -type f -exec sed -i '' "s/0.0.0/$1/g" {} +
|
||||
find chart/values.yaml -type f -exec sed -i '' "s/repository: controller/$2/g" {} +
|
||||
else
|
||||
find chart/ -type f -exec sed -i "s/0.0.0/$1/g" {} +
|
||||
find chart/values.yaml -type f -exec sed -i "s/repository: controller/$2/g" {} +
|
||||
fi
|
||||
Reference in New Issue
Block a user