From f98b0bc5eeb9edb8d277f5d36d3d2ce0c68a6497 Mon Sep 17 00:00:00 2001 From: Evgenii Date: Sat, 22 Jun 2024 09:47:24 +0200 Subject: [PATCH 01/15] fix(backup): find race and disable search in subdir, always test latest backup image in e2e, bump backup to 0.3.x (#1011) Co-authored-by: brokenpip3 --- Makefile | 15 +++- backup/pvc/Makefile | 8 ++- backup/pvc/VERSION.txt | 2 +- backup/pvc/bin/get-latest.sh | 33 ++++++++- backup/pvc/bin/run.sh | 69 +++++++++++++++++-- backup/pvc/config.env | 1 + backup/pvc/e2e/get-latest/test.sh | 17 +++++ backup/pvc/e2e/limit_backup_count/test.sh | 13 ++-- .../e2e/limit_backup_count_no_backups/test.sh | 14 +++- .../test.sh | 6 +- test/e2e/restorebackup_test.go | 2 +- 11 files changed, 160 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index 10c3e047..bdd8d4e3 100644 --- a/Makefile +++ b/Makefile @@ -89,11 +89,18 @@ test: ## Runs the go tests @RUNNING_TESTS=1 go test -tags "$(BUILDTAGS) cgo" $(PACKAGES_FOR_UNIT_TESTS) .PHONY: e2e -e2e: deepcopy-gen manifests ## Runs e2e tests, you can use EXTRA_ARGS +e2e: deepcopy-gen manifests backup-kind-load ## Runs e2e tests, you can use EXTRA_ARGS @echo "+ $@" RUNNING_TESTS=1 go test -parallel=1 "./test/e2e/" -ginkgo.v -tags "$(BUILDTAGS) cgo" -v -timeout 60m -run "$(E2E_TEST_SELECTOR)" \ -jenkins-api-hostname=$(JENKINS_API_HOSTNAME) -jenkins-api-port=$(JENKINS_API_PORT) -jenkins-api-use-nodeport=$(JENKINS_API_USE_NODEPORT) $(E2E_TEST_ARGS) +## Backup Section + +.PHONY: backup-kind-load +backup-kind-load: ## Load latest backup image in the cluster + @echo "+ $@" + make -C backup/pvc backup-kind-load + ## HELM Section .PHONY: helm @@ -370,6 +377,12 @@ kind-clean: ## Delete kind cluster @echo "+ $@" kind delete cluster --name $(KIND_CLUSTER_NAME) +.PHONY: kind-load-backup +kind-load-backup: + @echo "+ $@" + make -C + kind delete cluster --name $(KIND_CLUSTER_NAME) + .PHONY: bats-tests IMAGE_NAME := quay.io/$(QUAY_ORGANIZATION)/$(QUAY_REGISTRY):$(GITCOMMIT)-amd64 BUILD_PRESENT := $(shell docker images |grep -q ${IMAGE_NAME}) diff --git a/backup/pvc/Makefile b/backup/pvc/Makefile index d8ec544f..4999ae7a 100644 --- a/backup/pvc/Makefile +++ b/backup/pvc/Makefile @@ -118,6 +118,12 @@ docker-release-latest: docker-build ## Release image with latest tags (in additi docker-release: docker-release-version docker-release-latest ## Release image with version and latest tags (in addition to build tag) @echo "+ $@" +.PHONY: backup-kind-load +backup-kind-load: docker-build ## Build and load backup img in kind with e2e-test tag + @echo "+ $@" + docker tag quay.io/$(QUAY_ORGANIZATION)/$(QUAY_REGISTRY)-$(NAME):$(GITCOMMIT) quay.io/$(QUAY_ORGANIZATION)/$(QUAY_REGISTRY)-$(NAME):e2e-test + kind load docker-image quay.io/$(QUAY_ORGANIZATION)/$(QUAY_REGISTRY)-$(NAME):e2e-test --name $(KIND_CLUSTER_NAME) + # if this session isn't interactive, then we don't want to allocate a # TTY, which would fail, but if it is interactive, we do want to attach # so that the user can send e.g. ^C through. @@ -127,7 +133,7 @@ ifeq ($(INTERACTIVE), 1) endif .PHONY: docker-run -docker-run: docker-build ## Run the container in docker, you can use EXTRA_ARGS +docker-run: docker-build @echo "+ $@" docker run --rm -i $(DOCKER_FLAGS) \ quay.io/$(QUAY_ORGANIZATION)/$(QUAY_REGISTRY)-$(NAME):$(GITCOMMIT) $(ARGS) diff --git a/backup/pvc/VERSION.txt b/backup/pvc/VERSION.txt index b88fb90f..268b0334 100644 --- a/backup/pvc/VERSION.txt +++ b/backup/pvc/VERSION.txt @@ -1 +1 @@ -v0.2.5 +v0.3.0 diff --git a/backup/pvc/bin/get-latest.sh b/backup/pvc/bin/get-latest.sh index 519a4c6e..da8055f4 100644 --- a/backup/pvc/bin/get-latest.sh +++ b/backup/pvc/bin/get-latest.sh @@ -2,9 +2,38 @@ set -eo pipefail -[[ -z "${BACKUP_DIR}" ]] && echo "Required 'BACKUP_DIR' env not set" && exit 1 +is_backup_not_exist() { + local backup_dir="$1" + # Save the current value of 'set -e' + local previous_e + previous_e=$(set +e; :; echo $?) + + # Temporarily turn off 'set -e' + set +e + + # Run ls command to check if any files matching the pattern exist + ls "${backup_dir}"/*.tar.* 1> /dev/null 2>&1 + + # Store the exit status of the ls command + local ls_exit_status=$? + + # Restore the previous value of 'set -e' + [ "$previous_e" = "0" ] && set -e + + # Return true if ls command succeeded (no files found), otherwise return false + [ $ls_exit_status -ne 0 ] +} + +[[ -z "${BACKUP_DIR}" ]] && { echo "Required 'BACKUP_DIR' env not set"; exit 1; } + +# Check if we have any backup +if is_backup_not_exist "${BACKUP_DIR}"; then + echo "-1" + exit 0 +fi + # Search for all the tar.* inside the backup dir to support the migration between gzip vs zstd -latest=$(find ${BACKUP_DIR} -name '*.tar.*' -exec basename {} \; | sort -g | tail -n 1) +latest=$(find "${BACKUP_DIR}"/*.tar.* -maxdepth 0 -exec basename {} \; | sort -g | tail -n 1) if [[ "${latest}" == "" ]]; then echo "-1" diff --git a/backup/pvc/bin/run.sh b/backup/pvc/bin/run.sh index 50123d5e..4b605201 100644 --- a/backup/pvc/bin/run.sh +++ b/backup/pvc/bin/run.sh @@ -2,15 +2,70 @@ set -eo pipefail -[[ -z "${BACKUP_DIR}" ]] && echo "Required 'BACKUP_DIR' env not set" && exit 1; -[[ -z "${JENKINS_HOME}" ]] && echo "Required 'JENKINS_HOME' env not set" && exit 1; +# Use 60 as default in case BACKUP_CLEANUP_INTERVAL did not set +BACKUP_CLEANUP_INTERVAL=${BACKUP_CLEANUP_INTERVAL:=60} + +# Ensure required environment variables are set +check_env_var() { + if [[ -z "${!1}" ]]; then + echo "Required '$1' environment variable is not set" + exit 1 + fi +} + +is_backup_not_exist() { + local backup_dir="$1" + # Save the current value of 'set -e' + local previous_e + previous_e=$(set +e; :; echo $?) + + # Temporarily turn off 'set -e' + set +e + + # Run ls command to check if any files matching the pattern exist + ls "${backup_dir}"/*.tar.* 1> /dev/null 2>&1 + + # Store the exit status of the ls command + local ls_exit_status=$? + + # Restore the previous value of 'set -e' + [ "$previous_e" = "0" ] && set -e + + # Return true if ls command succeeded (no files found), otherwise return false + [ $ls_exit_status -ne 0 ] +} + +# Function to find exceeding backups +find_exceeding_backups() { + local backup_dir="$1" + local backup_count="$2" + # Check if we have any backup + if is_backup_not_exist "${backup_dir}"; then + echo "backups not found in ${backup_dir}" >&2 + return + fi + find "${backup_dir}"/*.tar.zstd -maxdepth 0 -exec basename {} \; | sort -gr | tail -n +$((backup_count +1)) +} + +check_env_var "BACKUP_DIR" +check_env_var "JENKINS_HOME" + +if [[ -z "${BACKUP_COUNT}" ]]; then + echo "ATTENTION! No BACKUP_COUNT set, it means you MUST delete old backups manually or by custom script" +else + echo "Retaining only the ${BACKUP_COUNT} most recent backups, cleanup occurs every ${BACKUP_CLEANUP_INTERVAL} seconds" +fi while true; do - sleep 10 - if [[ ! -z "${BACKUP_COUNT}" ]]; then - echo "Trimming to only ${BACKUP_COUNT} recent backups in preparation for new backup" - #TODO: add the list of exceeding backup before delete - find ${BACKUP_DIR} -maxdepth 1 -name '*.tar.zstd' -exec basename {} \; | sort -gr | tail -n +$((BACKUP_COUNT +1)) | xargs -I '{}' rm ${BACKUP_DIR}/'{}' + sleep "$BACKUP_CLEANUP_INTERVAL" + if [[ -n "${BACKUP_COUNT}" ]]; then + exceeding_backups=$(find_exceeding_backups "${BACKUP_DIR}" "${BACKUP_COUNT}") + if [[ -n "$exceeding_backups" ]]; then + echo "Removing backups: $(echo "$exceeding_backups" | tr '\n' ', ' | sed 's/,$//')" + echo "$exceeding_backups" | while read -r file; do + rm "${BACKUP_DIR}/${file}" + done + fi fi done diff --git a/backup/pvc/config.env b/backup/pvc/config.env index 64a40754..e2b89225 100644 --- a/backup/pvc/config.env +++ b/backup/pvc/config.env @@ -4,3 +4,4 @@ QUAY_ORGANIZATION=jenkins-kubernetes-operator QUAY_REGISTRY=backup UID=1000 GID=1000 +KIND_CLUSTER_NAME=jenkins diff --git a/backup/pvc/e2e/get-latest/test.sh b/backup/pvc/e2e/get-latest/test.sh index a0aabe9a..e8b4eb48 100755 --- a/backup/pvc/e2e/get-latest/test.sh +++ b/backup/pvc/e2e/get-latest/test.sh @@ -30,6 +30,9 @@ touch ${BACKUP_DIR}/8.tar.zstd touch ${BACKUP_DIR}/9.tar.zstd touch ${BACKUP_DIR}/10.tar.zstd touch ${BACKUP_DIR}/11.tar.zstd +# Emulate backup creation +BACKUP_TMP_DIR=$(mktemp -d --tmpdir="${BACKUP_DIR}") +touch ${BACKUP_TMP_DIR}/12.tar.zstd # Create an instance of the container under testing cid="$(docker run -e JENKINS_HOME=${JENKINS_HOME} -v ${JENKINS_HOME}:${JENKINS_HOME}:ro -e BACKUP_DIR=${BACKUP_DIR} -v ${BACKUP_DIR}:${BACKUP_DIR}:rw -d ${docker_image})" @@ -38,10 +41,20 @@ echo "Docker container ID '${cid}'" # Remove test directory and container afterwards trap "docker rm -vf $cid > /dev/null;rm -rf ${BACKUP_DIR};rm -rf ${JENKINS_HOME}" EXIT +echo "Try to get latest against 11 backups and one in progress" latest=$(docker exec ${cid} /bin/bash -c "JENKINS_HOME=${RESTORE_FOLDER};/home/user/bin/get-latest.sh") + rm ${BACKUP_DIR}/*.tar.zstd +echo "Try to get latest against one in progress" empty_latest=$(docker exec ${cid} /bin/bash -c "JENKINS_HOME=${RESTORE_FOLDER};/home/user/bin/get-latest.sh") +rmdir ${BACKUP_DIR}/lost+found +rm ${BACKUP_TMP_DIR}/*.tar.zstd +rmdir ${BACKUP_TMP_DIR} +echo "Try to get latest against empty dir" +empty_dir_latest=$(docker exec ${cid} /bin/bash -c "JENKINS_HOME=${RESTORE_FOLDER};/home/user/bin/get-latest.sh") + + if [[ "${DEBUG}" ]]; then docker logs ${cid} ls -la ${BACKUP_DIR} @@ -55,5 +68,9 @@ if [[ ! "${empty_latest}" == "-1" ]]; then echo "Latest backup number should be '-1' but is '${empty_latest}'" exit 1 fi +if [[ ! "${empty_dir_latest}" == "-1" ]]; then + echo "Latest backup number should be '-1' but is '${empty_dir_latest}'" + exit 1 +fi echo PASS diff --git a/backup/pvc/e2e/limit_backup_count/test.sh b/backup/pvc/e2e/limit_backup_count/test.sh index 469b3e44..cbe1ecd4 100755 --- a/backup/pvc/e2e/limit_backup_count/test.sh +++ b/backup/pvc/e2e/limit_backup_count/test.sh @@ -1,6 +1,8 @@ #!/bin/bash set -eo pipefail +echo "Running limit_backup_count e2e test..." + [[ "${DEBUG}" ]] && set -x # set current working directory to the directory of the script @@ -30,17 +32,20 @@ touch ${BACKUP_DIR}/8.tar.zstd touch ${BACKUP_DIR}/9.tar.zstd touch ${BACKUP_DIR}/10.tar.zstd touch ${BACKUP_DIR}/11.tar.zstd +# Emulate backup creation +BACKUP_TMP_DIR=$(mktemp -d --tmpdir="${BACKUP_DIR}") +touch ${BACKUP_TMP_DIR}/12.tar.zstd # Create an instance of the container under testing -cid="$(docker run -e BACKUP_COUNT=2 -e JENKINS_HOME=${JENKINS_HOME} -v ${JENKINS_HOME}:${JENKINS_HOME}:ro -e BACKUP_DIR=${BACKUP_DIR} -v ${BACKUP_DIR}:${BACKUP_DIR}:rw -d ${docker_image})" +cid="$(docker run -e BACKUP_CLEANUP_INTERVAL=1 -e BACKUP_COUNT=2 -e JENKINS_HOME=${JENKINS_HOME} -v ${JENKINS_HOME}:${JENKINS_HOME}:ro -e BACKUP_DIR=${BACKUP_DIR} -v ${BACKUP_DIR}:${BACKUP_DIR}:rw -d ${docker_image})" echo "Docker container ID '${cid}'" # Remove test directory and container afterwards trap "docker rm -vf $cid > /dev/null;rm -rf ${BACKUP_DIR};rm -rf ${JENKINS_HOME}" EXIT -sleep 11 -touch ${BACKUP_DIR}/12.tar.zstd -sleep 11 +sleep 2 +mv ${BACKUP_TMP_DIR}/12.tar.zstd ${BACKUP_DIR}/ +sleep 2 if [[ "${DEBUG}" ]]; then docker logs ${cid} diff --git a/backup/pvc/e2e/limit_backup_count_no_backups/test.sh b/backup/pvc/e2e/limit_backup_count_no_backups/test.sh index 531ac3da..1b90acd8 100755 --- a/backup/pvc/e2e/limit_backup_count_no_backups/test.sh +++ b/backup/pvc/e2e/limit_backup_count_no_backups/test.sh @@ -1,6 +1,8 @@ #!/bin/bash set -eo pipefail +echo "Running limit_backup_count_no_backups e2e test..." + [[ "${DEBUG}" ]] && set -x # set current working directory to the directory of the script @@ -19,7 +21,7 @@ mkdir -p ${BACKUP_DIR} mkdir -p ${JENKINS_HOME} # Create an instance of the container under testing -cid="$(docker run -e BACKUP_COUNT=2 -e JENKINS_HOME=${JENKINS_HOME} -v ${JENKINS_HOME}:${JENKINS_HOME}:ro -e BACKUP_DIR=${BACKUP_DIR} -v ${BACKUP_DIR}:${BACKUP_DIR}:rw -d ${docker_image})" +cid="$(docker run -e BACKUP_CLEANUP_INTERVAL=1 -e BACKUP_COUNT=2 -e JENKINS_HOME=${JENKINS_HOME} -v ${JENKINS_HOME}:${JENKINS_HOME}:ro -e BACKUP_DIR=${BACKUP_DIR} -v ${BACKUP_DIR}:${BACKUP_DIR}:rw -d ${docker_image})" echo "Docker container ID '${cid}'" # Remove test directory and container afterwards @@ -27,8 +29,16 @@ trap "docker rm -vf $cid > /dev/null;rm -rf ${BACKUP_DIR};rm -rf ${JENKINS_HOME} # container should be running echo 'Checking if container is running' -sleep 11 +sleep 3 +set +e docker exec ${cid} echo +exit_code=$? +set -e +if [ $exit_code -ne 0 ]; then + echo "container terminated with following logs:" + docker logs "${cid}" + exit 1 +fi echo 'Container is running' echo PASS diff --git a/backup/pvc/e2e/tmp_dir_clean_after_backup_creation/test.sh b/backup/pvc/e2e/tmp_dir_clean_after_backup_creation/test.sh index 662809c8..f373543c 100755 --- a/backup/pvc/e2e/tmp_dir_clean_after_backup_creation/test.sh +++ b/backup/pvc/e2e/tmp_dir_clean_after_backup_creation/test.sh @@ -1,6 +1,8 @@ #!/bin/bash set -eo pipefail +echo "Running tmp_dir_clean_after_backup_creation e2e test..." + [[ "${DEBUG}" ]] && set -x # set current working directory to the directory of the script @@ -18,7 +20,7 @@ BACKUP_DIR="$(pwd)/backup" mkdir -p ${BACKUP_DIR} # Create an instance of the container under testing -cid="$(docker run -e JENKINS_HOME=${JENKINS_HOME} -v ${JENKINS_HOME}:${JENKINS_HOME}:ro -e BACKUP_DIR=${BACKUP_DIR} -v ${BACKUP_DIR}:${BACKUP_DIR}:rw -d ${docker_image})" +cid="$(docker run -e BACKUP_CLEANUP_INTERVAL=1 -e JENKINS_HOME=${JENKINS_HOME} -v ${JENKINS_HOME}:${JENKINS_HOME}:ro -e BACKUP_DIR=${BACKUP_DIR} -v ${BACKUP_DIR}:${BACKUP_DIR}:rw -d ${docker_image})" echo "Docker container ID '${cid}'" # Remove test directory and container afterwards @@ -28,6 +30,8 @@ backup_number=1 docker exec ${cid} /home/user/bin/backup.sh ${backup_number} [ "$(docker exec ${cid} ls /tmp | grep 'tmp')" ] && echo "tmp directory not empty" && exit 1; +# We should also check backup directory, since after #1000 we create temp directory at backup filesystem +[ "$(docker exec ${cid} ls ${BACKUP_DIR} | grep 'tmp')" ] && echo "backup dir consists temp directory" && exit 1; backup_file="${BACKUP_DIR}/${backup_number}.tar.zstd" [[ ! -f ${backup_file} ]] && echo "Backup file ${backup_file} not found" && exit 1; diff --git a/test/e2e/restorebackup_test.go b/test/e2e/restorebackup_test.go index aa90f6ae..43f2f5b4 100644 --- a/test/e2e/restorebackup_test.go +++ b/test/e2e/restorebackup_test.go @@ -157,7 +157,7 @@ func createJenkinsWithBackupAndRestoreConfigured(name, namespace string) *v1alph }, { Name: containerName, - Image: "virtuslab/jenkins-operator-backup-pvc:v0.1.1", + Image: "quay.io/jenkins-kubernetes-operator/backup-pvc:e2e-test", ImagePullPolicy: corev1.PullIfNotPresent, Env: []corev1.EnvVar{ { From e0f52b5bdf02104b31ec1627143032510803622a Mon Sep 17 00:00:00 2001 From: Luigi Operoso <40476330+brokenpip3@users.noreply.github.com> Date: Sat, 22 Jun 2024 16:27:38 +0200 Subject: [PATCH 02/15] chore(operator): update jenkins latest img and plugins, use latest backup in bats tests (#1022) --- Makefile | 10 ++-------- chart/jenkins-operator/README.md | 2 +- chart/jenkins-operator/values.yaml | 6 +++--- config.base.env | 2 +- pkg/configuration/user/seedjobs/seedjobs.go | 2 +- pkg/configuration/user/seedjobs/seedjobs_test.go | 2 +- pkg/constants/constants.go | 2 +- pkg/plugins/base_plugins.go | 2 +- test/bats/1-deploy.bats | 14 +++++++++----- test/bats/2-deploy-with-more-options.bats | 6 ++++-- test/bats/3-deploy-with-webhook.bats | 10 ++++++---- test/e2e/configuration_test.go | 2 +- test/e2e/test_utility.go | 2 +- test/helm/helm_test.go | 2 +- 14 files changed, 33 insertions(+), 31 deletions(-) diff --git a/Makefile b/Makefile index bdd8d4e3..e4ef73b2 100644 --- a/Makefile +++ b/Makefile @@ -377,22 +377,16 @@ kind-clean: ## Delete kind cluster @echo "+ $@" kind delete cluster --name $(KIND_CLUSTER_NAME) -.PHONY: kind-load-backup -kind-load-backup: - @echo "+ $@" - make -C - kind delete cluster --name $(KIND_CLUSTER_NAME) - .PHONY: bats-tests IMAGE_NAME := quay.io/$(QUAY_ORGANIZATION)/$(QUAY_REGISTRY):$(GITCOMMIT)-amd64 BUILD_PRESENT := $(shell docker images |grep -q ${IMAGE_NAME}) ifndef BUILD_PRESENT -bats-tests: container-runtime-build-amd64 ## Run bats tests +bats-tests: backup-kind-load container-runtime-build-amd64 ## Run bats tests @echo "+ $@" kind load docker-image ${IMAGE_NAME} --name $(KIND_CLUSTER_NAME) OPERATOR_IMAGE="${IMAGE_NAME}" TERM=xterm bats -T -p test/bats else -bats-tests: ## Run bats tests +bats-tests: backup-kind-load @echo "+ $@" OPERATOR_IMAGE="${IMAGE_NAME}" TERM=xterm bats -T -p test/bats endif diff --git a/chart/jenkins-operator/README.md b/chart/jenkins-operator/README.md index 4ce0beb7..2c2b2fc1 100644 --- a/chart/jenkins-operator/README.md +++ b/chart/jenkins-operator/README.md @@ -54,7 +54,7 @@ Kubernetes native operator which fully manages Jenkins on Kubernetes | jenkins.enabled | bool | `true` | | | jenkins.env | list | `[]` | | | jenkins.hostAliases | object | `{}` | | -| jenkins.image | string | `"jenkins/jenkins:2.452.1-lts"` | | +| jenkins.image | string | `"jenkins/jenkins:2.452.2-lts"` | | | jenkins.imagePullPolicy | string | `"Always"` | | | jenkins.imagePullSecrets | list | `[]` | | | jenkins.labels | object | `{}` | | diff --git a/chart/jenkins-operator/values.yaml b/chart/jenkins-operator/values.yaml index f2eba788..cc878194 100644 --- a/chart/jenkins-operator/values.yaml +++ b/chart/jenkins-operator/values.yaml @@ -36,7 +36,7 @@ jenkins: # image is the name (and tag) of the Jenkins instance # Default: jenkins/jenkins:lts # It's recommended to use LTS (tag: "lts") version - image: jenkins/jenkins:2.452.1-lts + image: jenkins/jenkins:2.452.2-lts # env contains jenkins container environment variables env: [] @@ -94,7 +94,7 @@ jenkins: # # basePlugins: # - name: kubernetes - # version: 4238.v41b_3ef14a_5d8 + # version: 4246.v5a_12b_1fe120e # - name: workflow-job # version: 1400.v7fd111b_ec82f # - name: workflow-aggregator @@ -139,7 +139,7 @@ jenkins: # repositoryUrl: https://github.com/jenkinsci/kubernetes-operator.git seedJobs: [] - # SeedJobAgentImage defines the image that will be used by the seed job agent. If not defined jenkins/inbound-agent:3206.vb_15dcf73f6a_9-3 will be used. + # SeedJobAgentImage defines the image that will be used by the seed job agent. If not defined jenkins/inbound-agent:3248.v65ecb_254c298-6 will be used. seedJobAgentImage: "" # Resource limit/request for Jenkins diff --git a/config.base.env b/config.base.env index 5d6787d1..f535e8bb 100644 --- a/config.base.env +++ b/config.base.env @@ -7,7 +7,7 @@ GO_VERSION="1.15.6" HELM_VERSION="3.12.3" IMAGE_PULL_MODE="local" KIND_CLUSTER_NAME="jenkins" -LATEST_LTS_VERSION="2.452.1" +LATEST_LTS_VERSION="2.452.2" NAME="kubernetes-operator" NAMESPACE="default" OPERATOR_SDK_VERSION="1.3.0" diff --git a/pkg/configuration/user/seedjobs/seedjobs.go b/pkg/configuration/user/seedjobs/seedjobs.go index 64b54ffa..d6b0f598 100644 --- a/pkg/configuration/user/seedjobs/seedjobs.go +++ b/pkg/configuration/user/seedjobs/seedjobs.go @@ -46,7 +46,7 @@ const ( AgentName = "seed-job-agent" // DefaultAgentImage is the default image used for the seed-job agent - defaultAgentImage = "jenkins/inbound-agent:3248.v65ecb_254c298-4" + defaultAgentImage = "jenkins/inbound-agent:3248.v65ecb_254c298-6" creatingGroovyScriptName = "seed-job-groovy-script.groovy" diff --git a/pkg/configuration/user/seedjobs/seedjobs_test.go b/pkg/configuration/user/seedjobs/seedjobs_test.go index 30686749..ff517bf1 100644 --- a/pkg/configuration/user/seedjobs/seedjobs_test.go +++ b/pkg/configuration/user/seedjobs/seedjobs_test.go @@ -113,7 +113,7 @@ func TestEnsureSeedJobs(t *testing.T) { var agentDeployment appsv1.Deployment err = fakeClient.Get(ctx, types.NamespacedName{Namespace: jenkins.Namespace, Name: agentDeploymentName(*jenkins, AgentName)}, &agentDeployment) assert.NoError(t, err) - assert.Equal(t, "jenkins/inbound-agent:3248.v65ecb_254c298-4", agentDeployment.Spec.Template.Spec.Containers[0].Image) + assert.Equal(t, "jenkins/inbound-agent:3248.v65ecb_254c298-6", agentDeployment.Spec.Template.Spec.Containers[0].Image) assert.Equal(t, "JENKINS_WEB_SOCKET", agentDeployment.Spec.Template.Spec.Containers[0].Env[0].Name) assert.Equal(t, "true", agentDeployment.Spec.Template.Spec.Containers[0].Env[0].Value) }) diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index 7e26f411..e43f87c0 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -8,7 +8,7 @@ const ( // SeedJobSuffix is a suffix added for all seed jobs SeedJobSuffix = "job-dsl-seed" // DefaultJenkinsMasterImage is the default Jenkins master docker image - DefaultJenkinsMasterImage = "jenkins/jenkins:2.452.1-lts" + DefaultJenkinsMasterImage = "jenkins/jenkins:2.452.2-lts" // DefaultHTTPPortInt32 is the default Jenkins HTTP port DefaultHTTPPortInt32 = int32(8080) // DefaultSlavePortInt32 is the default Jenkins port for slaves diff --git a/pkg/plugins/base_plugins.go b/pkg/plugins/base_plugins.go index 8f35410f..48982e02 100644 --- a/pkg/plugins/base_plugins.go +++ b/pkg/plugins/base_plugins.go @@ -4,7 +4,7 @@ const ( configurationAsCodePlugin = "configuration-as-code:1810.v9b_c30a_249a_4c" gitPlugin = "git:5.2.2" jobDslPlugin = "job-dsl:1.87" - kubernetesPlugin = "kubernetes:4238.v41b_3ef14a_5d8" + kubernetesPlugin = "kubernetes:4246.v5a_12b_1fe120e" kubernetesCredentialsProviderPlugin = "kubernetes-credentials-provider:1.262.v2670ef7ea_0c5" workflowAggregatorPlugin = "workflow-aggregator:596.v8c21c963d92d" workflowJobPlugin = "workflow-job:1400.v7fd111b_ec82f" diff --git a/test/bats/1-deploy.bats b/test/bats/1-deploy.bats index 2155f1ac..5a6e6d38 100644 --- a/test/bats/1-deploy.bats +++ b/test/bats/1-deploy.bats @@ -33,8 +33,9 @@ diag() { --set namespace=${DETIK_CLIENT_NAMESPACE} \ --set operator.image=${OPERATOR_IMAGE} \ --set jenkins.latestPlugins=true \ - --set jenkins.image="jenkins/jenkins:2.452.1-lts" \ + --set jenkins.image="jenkins/jenkins:2.452.2-lts" \ --set jenkins.backup.makeBackupBeforePodDeletion=false \ + --set jenkins.backup.image=quay.io/jenkins-kubernetes-operator/backup-pvc:e2e-test \ --set jenkins.seedJobs[0].id=seed-job \ --set jenkins.seedJobs[0].targets="cicd/jobs/*.jenkins" \ --set jenkins.seedJobs[0].description="jobs-from-operator-repo" \ @@ -126,16 +127,18 @@ diag() { #bats test_tags=phase:helm,scenario:vanilla @test "1.10 Helm: check Jenkins seed job status and logs" { [[ ! -f "chart/jenkins-operator/deploy.tmp" ]] && skip "Jenkins helm chart have not been deployed correctly" + run try "at most 20 times every 10s to get pods named 'seed-job-agent-jenkins-' and verify that '.status.containerStatuses[?(@.name==\"jnlp\")].ready' is 'true'" + assert_success + run verify "there is 1 deployment named 'seed-job-agent-jenkins'" assert_success run verify "there is 1 pod named 'seed-job-agent-jenkins-'" assert_success - run try "at most 20 times every 10s to get pods named 'seed-job-agent-jenkins-' and verify that '.status.containerStatuses[?(@.name==\"jnlp\")].ready' is 'true'" - assert_success + sleep 5 - run ${KUBECTL} logs -l app=seed-job-agent-selector + run ${KUBECTL} logs -l app=seed-job-agent-selector --tail=20000 assert_success assert_output --partial 'INFO: Connected' @@ -150,8 +153,9 @@ diag() { --set namespace=${DETIK_CLIENT_NAMESPACE} \ --set operator.image=${OPERATOR_IMAGE} \ --set jenkins.latestPlugins=true \ - --set jenkins.image="jenkins/jenkins:2.452.1-lts" \ + --set jenkins.image="jenkins/jenkins:2.452.2-lts" \ --set jenkins.backup.makeBackupBeforePodDeletion=false \ + --set jenkins.backup.image=quay.io/jenkins-kubernetes-operator/backup-pvc:e2e-test \ chart/jenkins-operator assert_success assert ${HELM} status default diff --git a/test/bats/2-deploy-with-more-options.bats b/test/bats/2-deploy-with-more-options.bats index d4ce27d3..c685e57e 100644 --- a/test/bats/2-deploy-with-more-options.bats +++ b/test/bats/2-deploy-with-more-options.bats @@ -29,8 +29,9 @@ setup() { --set operator.image=${OPERATOR_IMAGE} \ --set jenkins.latestPlugins=true \ --set jenkins.nodeSelector.batstest=yep \ - --set jenkins.image="jenkins/jenkins:2.452.1-lts" \ + --set jenkins.image="jenkins/jenkins:2.452.2-lts" \ --set jenkins.backup.makeBackupBeforePodDeletion=false \ + --set jenkins.backup.image=quay.io/jenkins-kubernetes-operator/backup-pvc:e2e-test \ jenkins-operator/jenkins-operator --version=$(cat VERSION.txt | sed 's/v//') assert_success assert ${HELM} status options @@ -101,8 +102,9 @@ setup() { --set operator.image=${OPERATOR_IMAGE} \ --set jenkins.latestPlugins=true \ --set jenkins.nodeSelector.batstest=yep \ - --set jenkins.image="jenkins/jenkins:2.452.1-lts" \ + --set jenkins.image="jenkins/jenkins:2.452.2-lts" \ --set jenkins.backup.makeBackupBeforePodDeletion=false \ + --set jenkins.backup.image=quay.io/jenkins-kubernetes-operator/backup-pvc:e2e-test \ chart/jenkins-operator assert_success assert ${HELM} status options diff --git a/test/bats/3-deploy-with-webhook.bats b/test/bats/3-deploy-with-webhook.bats index c832f82f..9e6343a8 100644 --- a/test/bats/3-deploy-with-webhook.bats +++ b/test/bats/3-deploy-with-webhook.bats @@ -29,8 +29,9 @@ setup() { --set namespace=${DETIK_CLIENT_NAMESPACE} \ --set operator.image=${OPERATOR_IMAGE} \ --set jenkins.latestPlugins=true \ - --set jenkins.image="jenkins/jenkins:2.452.1-lts" \ + --set jenkins.image="jenkins/jenkins:2.452.2-lts" \ --set jenkins.backup.makeBackupBeforePodDeletion=true \ + --set jenkins.backup.image=quay.io/jenkins-kubernetes-operator/backup-pvc:e2e-test \ --set webhook.enabled=true \ jenkins-operator/jenkins-operator --version=$(cat VERSION.txt | sed 's/v//') assert_success @@ -48,7 +49,7 @@ setup() { run verify "there is 1 pod named 'webhook-jenkins-operator-'" assert_success - run try "at most 20 times every 10s to get pods named 'webhook-jenkins-operator-' and verify that '.status.containerStatuses[?(@.name==\"jenkins-operator\")].ready' is 'true'" + run try "at most 50 times every 5s to get pod named 'webhook-jenkins-operator-' and verify that '.status.containerStatuses[?(@.name==\"jenkins-operator\")].ready' is 'true'" assert_success run ${KUBECTL} rollout restart deployment webhook-jenkins-operator @@ -59,7 +60,7 @@ setup() { @test "3.4 Helm: check Jenkins Pod status" { [[ ! -f "chart/jenkins-operator/deploy.tmp" ]] && skip "Jenkins helm chart have not been deployed correctly" - run try "at most 30 times every 10s to get pods named 'jenkins-jenkins' and verify that '.status.containerStatuses[?(@.name==\"jenkins-master\")].ready' is 'true'" + run try "at most 30 times every 10s to get pod named 'jenkins-jenkins' and verify that '.status.containerStatuses[?(@.name==\"jenkins-master\")].ready' is 'true'" assert_success } @@ -86,8 +87,9 @@ setup() { --set namespace=${DETIK_CLIENT_NAMESPACE} \ --set operator.image=${OPERATOR_IMAGE} \ --set jenkins.latestPlugins=true \ - --set jenkins.image="jenkins/jenkins:2.452.1-lts" \ + --set jenkins.image="jenkins/jenkins:2.452.2-lts" \ --set jenkins.backup.makeBackupBeforePodDeletion=true \ + --set jenkins.backup.image=quay.io/jenkins-kubernetes-operator/backup-pvc:e2e-test \ --set webhook.enabled=true \ chart/jenkins-operator assert_success diff --git a/test/e2e/configuration_test.go b/test/e2e/configuration_test.go index 4bef64e0..596c8d7e 100644 --- a/test/e2e/configuration_test.go +++ b/test/e2e/configuration_test.go @@ -26,7 +26,7 @@ const e2e = "e2e" var expectedBasePluginsList = []plugins.Plugin{ plugins.Must(plugins.New("configuration-as-code:1810.v9b_c30a_249a_4c")), plugins.Must(plugins.New("git:5.2.2")), - plugins.Must(plugins.New("kubernetes:4238.v41b_3ef14a_5d8")), + plugins.Must(plugins.New("kubernetes:4246.v5a_12b_1fe120e")), plugins.Must(plugins.New("kubernetes-credentials-provider:1.262.v2670ef7ea_0c5")), plugins.Must(plugins.New("job-dsl:1.87")), plugins.Must(plugins.New("workflow-aggregator:596.v8c21c963d92d")), diff --git a/test/e2e/test_utility.go b/test/e2e/test_utility.go index b3900e41..fd0e0826 100644 --- a/test/e2e/test_utility.go +++ b/test/e2e/test_utility.go @@ -21,7 +21,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/envtest" ) -const JenkinsTestImage = "jenkins/jenkins:2.452.1-lts" +const JenkinsTestImage = "jenkins/jenkins:2.452.2-lts" var ( Cfg *rest.Config diff --git a/test/helm/helm_test.go b/test/helm/helm_test.go index 8fec9edb..f9e922a5 100644 --- a/test/helm/helm_test.go +++ b/test/helm/helm_test.go @@ -47,7 +47,7 @@ var _ = Describe("Jenkins Controller", func() { cmd := exec.Command("../../bin/helm", "upgrade", "jenkins", "../../chart/jenkins-operator", "--namespace", namespace.Name, "--debug", "--set-string", fmt.Sprintf("jenkins.namespace=%s", namespace.Name), - "--set-string", fmt.Sprintf("jenkins.image=%s", "jenkins/jenkins:2.452.1-lts"), + "--set-string", fmt.Sprintf("jenkins.image=%s", "jenkins/jenkins:2.452.2-lts"), "--set-string", fmt.Sprintf("operator.image=%s", *imageName), "--install") output, err := cmd.CombinedOutput() Expect(err).NotTo(HaveOccurred(), string(output)) From 5ef6c730deeb8936e85a33db72aa5e96046467ec Mon Sep 17 00:00:00 2001 From: DionJones615 Date: Tue, 25 Jun 2024 12:44:55 -0400 Subject: [PATCH 03/15] fix(backup): Duplicated backups & restores (#1021) --- .../backuprestore/backuprestore.go | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/pkg/configuration/backuprestore/backuprestore.go b/pkg/configuration/backuprestore/backuprestore.go index b2deb9ca..ed203a55 100644 --- a/pkg/configuration/backuprestore/backuprestore.go +++ b/pkg/configuration/backuprestore/backuprestore.go @@ -180,12 +180,7 @@ func (bar *BackupAndRestore) Restore(jenkinsClient jenkinsclient.Jenkins) error if err != nil { return err } - //TODO fix me because we're doing two saves unatomically - jenkins.Spec.Restore.RecoveryOnce = 0 - err = bar.Client.Update(context.TODO(), jenkins) - if err != nil { - return err - } + key := types.NamespacedName{ Namespace: jenkins.Namespace, Name: jenkins.Name, @@ -194,6 +189,12 @@ func (bar *BackupAndRestore) Restore(jenkinsClient jenkinsclient.Jenkins) error if err != nil { return err } + + jenkins.Spec.Restore.RecoveryOnce = 0 + err = bar.Client.Update(context.TODO(), jenkins) + if err != nil { + return err + } bar.Configuration.Jenkins = jenkins jenkins.Status.RestoredBackup = backupNumber @@ -223,6 +224,15 @@ func (bar *BackupAndRestore) Backup(setBackupDoneBeforePodDeletion bool) error { _, _, err := bar.Exec(podName, jenkins.Spec.Backup.ContainerName, command) if err == nil { + key := types.NamespacedName{ + Namespace: jenkins.Namespace, + Name: jenkins.Name, + } + err = bar.Client.Get(context.TODO(), key, jenkins) + if err != nil { + return err + } + bar.logger.V(log.VDebug).Info(fmt.Sprintf("Backup completed '%d', updating status", backupNumber)) if jenkins.Status.RestoredBackup == 0 { jenkins.Status.RestoredBackup = backupNumber From b722ef11aea481e643b4e7a41c57bedc3d082d36 Mon Sep 17 00:00:00 2001 From: Luigi Operoso <40476330+brokenpip3@users.noreply.github.com> Date: Tue, 25 Jun 2024 23:29:35 +0200 Subject: [PATCH 04/15] feat(backup): logs everything to stdout/err, implement lock file for both backup/restore (#1023) --- backup/pvc/VERSION.txt | 2 +- backup/pvc/bin/backup.sh | 54 +++++++++++++------ backup/pvc/bin/get-latest.sh | 8 +-- backup/pvc/bin/restore.sh | 44 ++++++++++----- backup/pvc/bin/run.sh | 11 ++-- backup/pvc/bin/utils.sh | 14 +++++ chart/jenkins-operator/README.md | 2 +- chart/jenkins-operator/values.yaml | 16 +++++- nix/website-shell.nix | 1 + .../latest/configuring-backup-and-restore.md | 18 ++++++- 10 files changed, 127 insertions(+), 43 deletions(-) create mode 100644 backup/pvc/bin/utils.sh diff --git a/backup/pvc/VERSION.txt b/backup/pvc/VERSION.txt index 268b0334..fb7a04cf 100644 --- a/backup/pvc/VERSION.txt +++ b/backup/pvc/VERSION.txt @@ -1 +1 @@ -v0.3.0 +v0.4.0 diff --git a/backup/pvc/bin/backup.sh b/backup/pvc/bin/backup.sh index eaa648f9..13d2f096 100644 --- a/backup/pvc/bin/backup.sh +++ b/backup/pvc/bin/backup.sh @@ -1,39 +1,63 @@ #!/usr/bin/env bash set -eo pipefail +source "$(dirname "$0")/utils.sh" -[[ ! $# -eq 1 ]] && echo "Usage: $0 backup_number" && exit 1; -[[ -z "${BACKUP_DIR}" ]] && echo "Required 'BACKUP_DIR' env not set" && exit 1; -[[ -z "${JENKINS_HOME}" ]] && echo "Required 'JENKINS_HOME' env not set" && exit 1; +[[ ! $# -eq 1 ]] && _log "ERROR" "Usage: $0 BACKUP_NUMBER" && exit 1 +[[ -z "${BACKUP_DIR}" ]] && _log "ERROR" "Required 'BACKUP_DIR' env not set" && exit 1 +[[ -z "${JENKINS_HOME}" ]] && _log "ERROR" "Required 'JENKINS_HOME' env not set" && exit 1 +BACKUP_RETRY_COUNT=${BACKUP_RETRY_COUNT:-3} +BACKUP_RETRY_INTERVAL=${BACKUP_RETRY_INTERVAL:-60} +BACKUP_NUMBER=$1 +TRAP_FILE="/tmp/_backup_${BACKUP_NUMBER}_is_running" +# --> Check if another backup process is running (operator restart/crash) +for ((i=0; i /dev/null 2>&1 - # Store the exit status of the ls command local ls_exit_status=$? - # Restore the previous value of 'set -e' [ "$previous_e" = "0" ] && set -e - # Return true if ls command succeeded (no files found), otherwise return false [ $ls_exit_status -ne 0 ] } -[[ -z "${BACKUP_DIR}" ]] && { echo "Required 'BACKUP_DIR' env not set"; exit 1; } +[[ -z "${BACKUP_DIR}" ]] && { _log "ERROR" "Required 'BACKUP_DIR' env not set"; exit 1; } # Check if we have any backup if is_backup_not_exist "${BACKUP_DIR}"; then diff --git a/backup/pvc/bin/restore.sh b/backup/pvc/bin/restore.sh index 994208d3..3a692625 100644 --- a/backup/pvc/bin/restore.sh +++ b/backup/pvc/bin/restore.sh @@ -1,29 +1,47 @@ #!/usr/bin/env bash set -eo pipefail +source "$(dirname "$0")/utils.sh" -[[ ! $# -eq 1 ]] && echo "Usage: $0 backup_number" && exit 1 -[[ -z "${BACKUP_DIR}" ]] && echo "Required 'BACKUP_DIR' env not set" && exit 1; -[[ -z "${JENKINS_HOME}" ]] && echo "Required 'JENKINS_HOME' env not set" && exit 1; +[[ ! $# -eq 1 ]] && _log "ERROR" "Usage: $0 " && exit 1 +[[ -z "${BACKUP_DIR}" ]] && _log "ERROR" "Required 'BACKUP_DIR' env not set" && exit 1 +[[ -z "${JENKINS_HOME}" ]] && _log "ERROR" "Required 'JENKINS_HOME' env not set" && exit 1 +BACKUP_NUMBER=$1 +RESTORE_RETRY_COUNT=${RESTORE_RETRY_COUNT:-10} +RESTORE_RETRY_INTERVAL=${RESTORE_RETRY_INTERVAL:-10} -backup_number=$1 -backup_file="${BACKUP_DIR}/${backup_number}" -echo "Running restore backup with backup number #${backup_number}" +# --> Check if another restore process is running (operator restart/crash) +TRAP_FILE="/tmp/_restore_${BACKUP_NUMBER}_is_running" +trap "rm -f ${TRAP_FILE}" SIGINT SIGTERM -if [[ -f "$backup_file.tar.gz" ]]; then - echo "Old format tar.gz found, restoring it" +for ((i=0; i&2 + _log "ERROR" "[run] backups not found in ${backup_dir}" return fi find "${backup_dir}"/*.tar.zstd -maxdepth 0 -exec basename {} \; | sort -gr | tail -n +$((backup_count +1)) @@ -51,9 +52,9 @@ check_env_var "BACKUP_DIR" check_env_var "JENKINS_HOME" if [[ -z "${BACKUP_COUNT}" ]]; then - echo "ATTENTION! No BACKUP_COUNT set, it means you MUST delete old backups manually or by custom script" + _log "WARNING" "[run] no BACKUP_COUNT set, it means you MUST delete old backups manually or by custom script" else - echo "Retaining only the ${BACKUP_COUNT} most recent backups, cleanup occurs every ${BACKUP_CLEANUP_INTERVAL} seconds" + _log "INFO" "[run] retaining only the ${BACKUP_COUNT} most recent backups, cleanup occurs every ${BACKUP_CLEANUP_INTERVAL} seconds" fi while true; @@ -62,7 +63,7 @@ do if [[ -n "${BACKUP_COUNT}" ]]; then exceeding_backups=$(find_exceeding_backups "${BACKUP_DIR}" "${BACKUP_COUNT}") if [[ -n "$exceeding_backups" ]]; then - echo "Removing backups: $(echo "$exceeding_backups" | tr '\n' ', ' | sed 's/,$//')" + _log "INFO" "[run] removing backups: $(echo "$exceeding_backups" | tr '\n' ', ' | sed 's/,$//')" echo "$exceeding_backups" | while read -r file; do rm "${BACKUP_DIR}/${file}" done diff --git a/backup/pvc/bin/utils.sh b/backup/pvc/bin/utils.sh new file mode 100644 index 00000000..718dfcf1 --- /dev/null +++ b/backup/pvc/bin/utils.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# Common utils + +_log() { + local level="$1" + local message="$2" + local timestamp=$(date '+%Y-%m-%d %H:%M:%S') + if [[ "$level" =~ ^(ERROR|ERR|error|err)$ ]]; then + echo "${timestamp} - ${level} - ${message}" > /proc/1/fd/2 + else + echo "${timestamp} - ${level} - ${message}" > /proc/1/fd/1 + echo "${timestamp} - ${level} - ${message}" >&2 + fi +} diff --git a/chart/jenkins-operator/README.md b/chart/jenkins-operator/README.md index 2c2b2fc1..e61ec2f6 100644 --- a/chart/jenkins-operator/README.md +++ b/chart/jenkins-operator/README.md @@ -30,7 +30,7 @@ Kubernetes native operator which fully manages Jenkins on Kubernetes | jenkins.backup.env[2].name | string | `"BACKUP_COUNT"` | | | jenkins.backup.env[2].value | string | `"3"` | | | jenkins.backup.getLatestAction[0] | string | `"/home/user/bin/get-latest.sh"` | | -| jenkins.backup.image | string | `"quay.io/jenkins-kubernetes-operator/backup-pvc:v0.2.6"` | | +| jenkins.backup.image | string | `"quay.io/jenkins-kubernetes-operator/backup-pvc:v0.4.1"` | | | jenkins.backup.interval | int | `30` | | | jenkins.backup.makeBackupBeforePodDeletion | bool | `true` | | | jenkins.backup.pvc.className | string | `""` | | diff --git a/chart/jenkins-operator/values.yaml b/chart/jenkins-operator/values.yaml index cc878194..2ecdb579 100644 --- a/chart/jenkins-operator/values.yaml +++ b/chart/jenkins-operator/values.yaml @@ -214,7 +214,7 @@ jenkins: # image used by backup feature # By default using prebuilt backup PVC image - image: quay.io/jenkins-kubernetes-operator/backup-pvc:v0.2.6 + image: quay.io/jenkins-kubernetes-operator/backup-pvc:v0.4.1 # containerName is backup container name containerName: backup @@ -262,6 +262,11 @@ jenkins: # BACKUP_DIR - path for storing backup files (default: "/backup") # JENKINS_HOME - path to jenkins home (default: "/jenkins-home") # BACKUP_COUNT - define how much recent backups will be kept + # Optional in case you want to modify the backup and restore retry logic + # BACKUP_RETRY_COUNT + # BACKUP_RETRY_INTERVAL + # RESTORE_RETRY_COUNT + # RESTORE_RETRY_INTERVAL env: - name: BACKUP_DIR value: /backup @@ -269,6 +274,15 @@ jenkins: value: /jenkins-home - name: BACKUP_COUNT value: "3" # keep only the 3 most recent backups + #- name: BACKUP_RETRY_COUNT + # value: "3" + #- name: BACKUP_RETRY_INTERVAL + # value: "60" + #- name: RESTORE_RETRY_COUNT + # value: "10" + #- name: RESTORE_RETRY_INTERVAL + # value: "10" + # volumeMounts holds the mount points for volumes volumeMounts: diff --git a/nix/website-shell.nix b/nix/website-shell.nix index cda6748c..72542585 100644 --- a/nix/website-shell.nix +++ b/nix/website-shell.nix @@ -4,6 +4,7 @@ let devShellPackages = [ hugo_099_pkgs.hugo #hugo pre-v100 pkgs.nodejs_21 #Node 1.21 + pkgs.helm-docs ]; baseUrl = ((builtins.fromTOML (builtins.readFile ../website/config.toml)).baseURL); in diff --git a/website/content/en/docs/Getting Started/latest/configuring-backup-and-restore.md b/website/content/en/docs/Getting Started/latest/configuring-backup-and-restore.md index 35acf866..86cccde8 100644 --- a/website/content/en/docs/Getting Started/latest/configuring-backup-and-restore.md +++ b/website/content/en/docs/Getting Started/latest/configuring-backup-and-restore.md @@ -2,7 +2,7 @@ title: "Configuring backup and restore" linkTitle: "Configuring backup and restore" weight: 5 -date: 2023-01-08 +date: 2024-06-25 description: > Prevent loss of job history --- @@ -115,3 +115,19 @@ spec: command: - /home/user/bin/get-latest.sh # this command is invoked on "backup" container to get last backup number before pod deletion; not having it in the CR may cause loss of data ``` + +#### Customizing pvc backup behaviour + +To prevent situations where the operator crashes or gets killed during a backup and restore process, a retry logic has been implemented. + +This logic can be customized by adjusting the following environment variables: + +* **Backup**: total time wait until giving up by default: 180s + * `BACKUP_RETRY_COUNT`: by default is `3` + * `BACKUP_RETRY_INTERVAL`: by default is `60` + +* **Restore**: total time wait until giving up by default: 100s + * `RESTORE_RETRY_COUNT`: by default is `10` + * `RESTORE_RETRY_INTERVAL`: by default is `10` + +You can adjust the retry logic based on the size of your backup and the duration of the restore process. From 0911dfe64a122d8727a383da40dfd9ed2c082a6b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Jun 2024 23:30:29 +0200 Subject: [PATCH 05/15] build(deps): bump the actions group across 1 directory with 2 updates (#1019) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/auto-codespell.yml | 4 ++-- .github/workflows/auto-gen-docs.yaml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/auto-codespell.yml b/.github/workflows/auto-codespell.yml index f912de2f..ef4aac50 100644 --- a/.github/workflows/auto-codespell.yml +++ b/.github/workflows/auto-codespell.yml @@ -24,10 +24,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/setup-python@v5.1.0 - - uses: DeterminateSystems/nix-installer-action@v10 + - uses: DeterminateSystems/nix-installer-action@v12 with: diagnostic-endpoint: "" - - uses: DeterminateSystems/magic-nix-cache-action@v4 + - uses: DeterminateSystems/magic-nix-cache-action@v7 with: diagnostic-endpoint: "" - name: Checkout diff --git a/.github/workflows/auto-gen-docs.yaml b/.github/workflows/auto-gen-docs.yaml index 1cca9075..e88ba259 100644 --- a/.github/workflows/auto-gen-docs.yaml +++ b/.github/workflows/auto-gen-docs.yaml @@ -33,10 +33,10 @@ jobs: runs-on: ubuntu-latest if: github.event_name == 'pull_request' || (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') steps: - - uses: DeterminateSystems/nix-installer-action@v10 + - uses: DeterminateSystems/nix-installer-action@v12 with: diagnostic-endpoint: "" - - uses: DeterminateSystems/magic-nix-cache-action@v4 + - uses: DeterminateSystems/magic-nix-cache-action@v7 with: diagnostic-endpoint: "" - name: Checkout code @@ -68,10 +68,10 @@ jobs: runs-on: ubuntu-latest if: github.event_name == 'pull_request' || (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') steps: - - uses: DeterminateSystems/nix-installer-action@v10 + - uses: DeterminateSystems/nix-installer-action@v12 with: diagnostic-endpoint: "" - - uses: DeterminateSystems/magic-nix-cache-action@v4 + - uses: DeterminateSystems/magic-nix-cache-action@v7 with: diagnostic-endpoint: "" - name: Checkout code From 5e962b26ae8324cdee21cff7b0d1fd531029003b Mon Sep 17 00:00:00 2001 From: Luigi Operoso <40476330+brokenpip3@users.noreply.github.com> Date: Tue, 2 Jul 2024 22:39:50 +0200 Subject: [PATCH 06/15] fix(operator): deprecated jnlpUrl (#1026) --- pkg/client/jenkins.go | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/pkg/client/jenkins.go b/pkg/client/jenkins.go index d0738d0b..0134ecaf 100644 --- a/pkg/client/jenkins.go +++ b/pkg/client/jenkins.go @@ -1,10 +1,11 @@ package client import ( + "bytes" "fmt" + "io/ioutil" "net/http" "net/http/cookiejar" - "regexp" "strings" "time" @@ -14,7 +15,6 @@ import ( var ( errorNotFound = errors.New("404") - regex = regexp.MustCompile("()(?P[a-z0-9]*)") ) // Jenkins defines Jenkins API. @@ -197,27 +197,34 @@ func isNotFoundError(err error) bool { } func (jenkins *jenkins) GetNodeSecret(name string) (string, error) { - var content string - r, err := jenkins.Requester.GetXML(fmt.Sprintf("/computer/%s/slave-agent.jnlp", name), &content, nil) + url := fmt.Sprintf("%s/scriptText", jenkins.Server) + script := fmt.Sprintf(`println(jenkins.model.Jenkins.getInstance().getComputer("%s").getJnlpMac())`, name) + payload := bytes.NewBufferString(fmt.Sprintf("script=%s", script)) + + req, err := http.NewRequest("POST", url, payload) if err != nil { return "", errors.WithStack(err) } - defer r.Body.Close() + req.SetBasicAuth(jenkins.Requester.BasicAuth.Username, jenkins.Requester.BasicAuth.Password) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - match := regex.FindStringSubmatch(content) - if match == nil { - return "", errors.New("Node secret cannot be parsed") + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err } - result := make(map[string]string) - - for i, name := range regex.SubexpNames() { - if i != 0 && name != "" { - result[name] = match[i] - } + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("failed to get node secret: %s", string(body)) } - return result["secret"], nil + return string(body), nil } // Returns the list of all plugins installed on the Jenkins server. From 57a1b6bfb93867dc74842ec2b6c1272e090c0210 Mon Sep 17 00:00:00 2001 From: Luigi Operoso <40476330+brokenpip3@users.noreply.github.com> Date: Thu, 4 Jul 2024 23:38:48 +0200 Subject: [PATCH 07/15] chore: prepare 0.8.1 bug fix release (#1028) --- VERSION.txt | 2 +- chart/jenkins-operator/README.md | 2 +- chart/jenkins-operator/values.yaml | 2 +- docs/404.html | 120 - docs/about/index.html | 166 - docs/about/index.xml | 22 - .../01/04/another-great-release/index.html | 684 -- .../featured-sunset-get.png | Bin 387442 -> 0 bytes ...87442_250x125_fill_catmullrom_smart1_2.png | Bin 27856 -> 0 bytes ...87442_600x300_fill_catmullrom_smart1_2.png | Bin 104183 -> 0 bytes ...61c86_387442_640x0_resize_catmullrom_2.png | Bin 94305 -> 0 bytes .../easy-documentation-with-docsy/index.html | 338 - .../10/06/the-second-blog-post/index.html | 684 -- docs/blog/index.html | 334 - docs/blog/index.xml | 839 -- docs/blog/news/index.html | 318 - docs/blog/news/index.xml | 466 - docs/blog/news/page/1/index.html | 1 - docs/blog/page/1/index.html | 1 - docs/blog/releases/index.html | 293 - docs/blog/releases/index.xml | 397 - docs/blog/releases/page/1/index.html | 1 - docs/community/index.html | 148 - docs/community/index.xml | 22 - docs/docs/developer-guide/index.html | 1411 --- docs/docs/developer-guide/index.xml | 23 - docs/docs/faq/index.html | 1167 --- docs/docs/faq/index.xml | 23 - docs/docs/getting-started/index.html | 1201 --- docs/docs/getting-started/index.xml | 221 - .../getting-started/latest/aks/index.html | 986 -- .../configuring-backup-and-restore/index.html | 1089 -- .../index.html | 1273 --- .../custom-backup-and-restore/index.html | 1158 --- .../latest/customizing-jenkins/index.html | 1163 --- .../latest/deploying-jenkins/index.html | 1066 -- docs/docs/getting-started/latest/index.html | 1243 --- docs/docs/getting-started/latest/index.xml | 5266 ---------- .../latest/installing-the-operator/index.html | 1954 ---- .../getting-started/latest/ldap/index.html | 1092 -- .../latest/openshift/index.html | 980 -- .../getting-started/latest/schema/index.html | 3649 ------- .../latest/separate-namespaces/index.html | 1531 --- .../getting-started/v0.1.x/aks/index.html | 986 -- .../v0.1.x/configuration/index.html | 1223 --- .../configure-backup-and-restore/index.html | 1060 -- .../v0.1.x/customization/index.html | 1067 -- .../v0.1.x/deploy-jenkins/index.html | 1039 -- .../v0.1.x/diagnostics/index.html | 991 -- docs/docs/getting-started/v0.1.x/index.html | 1218 --- docs/docs/getting-started/v0.1.x/index.xml | 2322 ----- .../index.html | 1285 --- .../getting-started/v0.1.x/scheme/index.html | 2412 ----- .../getting-started/v0.2.x/aks/index.html | 986 -- .../v0.2.x/configuration/index.html | 1264 --- .../configure-backup-and-restore/index.html | 1062 -- .../custom-backup-and-restore/index.html | 1158 --- .../v0.2.x/customization/index.html | 1138 --- .../v0.2.x/deploy-jenkins/index.html | 1033 -- .../v0.2.x/diagnostics/index.html | 991 -- docs/docs/getting-started/v0.2.x/index.html | 1226 --- docs/docs/getting-started/v0.2.x/index.xml | 2894 ------ .../v0.2.x/migration/index.html | 1045 -- .../getting-started/v0.2.x/schema/index.html | 2947 ------ .../getting-started/v0.3.x/aks/index.html | 986 -- .../v0.3.x/configuration/index.html | 1264 --- .../configure-backup-and-restore/index.html | 1062 -- .../custom-backup-and-restore/index.html | 1158 --- .../v0.3.x/customization/index.html | 1138 --- .../v0.3.x/deploy-jenkins/index.html | 1034 -- .../v0.3.x/diagnostics/index.html | 991 -- docs/docs/getting-started/v0.3.x/index.html | 1234 --- docs/docs/getting-started/v0.3.x/index.xml | 3164 ------ .../v0.3.x/migration/index.html | 1000 -- .../v0.3.x/notifications/index.html | 1079 -- .../getting-started/v0.3.x/schema/index.html | 3141 ------ .../getting-started/v0.4.x/aks/index.html | 986 -- .../v0.4.x/configuration/index.html | 1273 --- .../configure-backup-and-restore/index.html | 1062 -- .../custom-backup-and-restore/index.html | 1158 --- .../v0.4.x/customization/index.html | 1160 --- .../v0.4.x/deploy-jenkins/index.html | 1034 -- .../v0.4.x/diagnostics/index.html | 991 -- docs/docs/getting-started/v0.4.x/index.html | 1234 --- docs/docs/getting-started/v0.4.x/index.xml | 3713 ------- .../v0.4.x/notifications/index.html | 1079 -- .../v0.4.x/openshift/index.html | 1081 -- .../getting-started/v0.4.x/schema/index.html | 3597 ------- .../getting-started/v0.5.x/aks/index.html | 986 -- .../v0.5.x/configuration/index.html | 1273 --- .../configure-backup-and-restore/index.html | 1066 -- .../custom-backup-and-restore/index.html | 1158 --- .../v0.5.x/customization/index.html | 1163 --- .../v0.5.x/deploy-jenkins/index.html | 1034 -- .../v0.5.x/diagnostics/index.html | 991 -- docs/docs/getting-started/v0.5.x/index.html | 1234 --- docs/docs/getting-started/v0.5.x/index.xml | 3735 ------- .../v0.5.x/notifications/index.html | 1079 -- .../v0.5.x/openshift/index.html | 1081 -- .../getting-started/v0.5.x/schema/index.html | 3612 ------- .../getting-started/v0.6.x/aks/index.html | 986 -- .../configuring-backup-and-restore/index.html | 1072 -- .../index.html | 1273 --- .../custom-backup-and-restore/index.html | 1158 --- .../v0.6.x/customizing-jenkins/index.html | 1163 --- .../v0.6.x/deploying-jenkins/index.html | 1066 -- docs/docs/getting-started/v0.6.x/index.html | 1243 --- docs/docs/getting-started/v0.6.x/index.xml | 5128 ---------- .../v0.6.x/installing-the-operator/index.html | 1866 ---- .../getting-started/v0.6.x/ldap/index.html | 1092 -- .../v0.6.x/openshift/index.html | 989 -- .../getting-started/v0.6.x/schema/index.html | 3617 ------- .../v0.6.x/separate-namespaces/index.html | 1529 --- .../architecture-and-design/index.html | 1007 -- docs/docs/how-it-works/index.html | 1150 --- docs/docs/how-it-works/index.xml | 94 - .../jenkins-docker-images/index.html | 970 -- docs/docs/index.html | 1169 --- docs/docs/index.xml | 8820 ----------------- docs/docs/security/index.html | 1203 --- docs/docs/security/index.xml | 23 - docs/docs/troubleshooting/index.html | 1177 --- docs/docs/troubleshooting/index.xml | 23 - docs/favicons/android-144x144.png | Bin 4299 -> 0 bytes docs/favicons/android-192x192.png | Bin 5264 -> 0 bytes docs/favicons/android-36x36.png | Bin 980 -> 0 bytes docs/favicons/android-48x48.png | Bin 1071 -> 0 bytes docs/favicons/android-72x72.png | Bin 1926 -> 0 bytes docs/favicons/android-96x196.png | Bin 2309 -> 0 bytes docs/favicons/apple-touch-icon-180x180.png | Bin 13970 -> 0 bytes docs/favicons/favicon-1024.png | Bin 27759 -> 0 bytes docs/favicons/favicon-16x16.png | Bin 698 -> 0 bytes docs/favicons/favicon-256.png | Bin 8037 -> 0 bytes docs/favicons/favicon-32x32.png | Bin 1738 -> 0 bytes docs/favicons/favicon.ico | Bin 1150 -> 0 bytes docs/favicons/pwa-192x192.png | Bin 15262 -> 0 bytes docs/favicons/pwa-512x512.png | Bin 28434 -> 0 bytes docs/favicons/tile150x150.png | Bin 22582 -> 0 bytes docs/favicons/tile310x150.png | Bin 25706 -> 0 bytes docs/favicons/tile310x310.png | Bin 30051 -> 0 bytes docs/favicons/tile70x70.png | Bin 9174 -> 0 bytes docs/img/jenkins-credentials.png | Bin 87698 -> 0 bytes docs/img/jenkins-seed.png | Bin 157965 -> 0 bytes docs/img/jenkins.png | Bin 137961 -> 0 bytes docs/img/logo.svg | 6512 ------------ docs/img/phases.png | Bin 55883 -> 0 bytes docs/img/reconcile.png | Bin 18498 -> 0 bytes docs/index.html | 272 - docs/index.xml | 839 -- ...14049168ac469a1faa0939560f0b370565becab.js | 5 - docs/robots.txt | 1 - ...084a9dc4dedee4e80eb3724fe530520541a4ec.css | 7 - docs/search/index.html | 138 - docs/sitemap.xml | 476 - docs/webfonts/fa-brands-400.eot | Bin 115052 -> 0 bytes docs/webfonts/fa-brands-400.svg | 1131 --- docs/webfonts/fa-brands-400.ttf | Bin 114816 -> 0 bytes docs/webfonts/fa-brands-400.woff | Bin 73920 -> 0 bytes docs/webfonts/fa-brands-400.woff2 | Bin 63352 -> 0 bytes docs/webfonts/fa-regular-400.eot | Bin 40744 -> 0 bytes docs/webfonts/fa-regular-400.svg | 471 - docs/webfonts/fa-regular-400.ttf | Bin 40516 -> 0 bytes docs/webfonts/fa-regular-400.woff | Bin 18212 -> 0 bytes docs/webfonts/fa-regular-400.woff2 | Bin 14960 -> 0 bytes docs/webfonts/fa-solid-900.eot | Bin 160768 -> 0 bytes docs/webfonts/fa-solid-900.svg | 2235 ----- docs/webfonts/fa-solid-900.ttf | Bin 160548 -> 0 bytes docs/webfonts/fa-solid-900.woff | Bin 76632 -> 0 bytes docs/webfonts/fa-solid-900.woff2 | Bin 59616 -> 0 bytes nix/website-build.nix | 6 +- test/bats/1-deploy.bats | 4 +- test/bats/2-deploy-with-more-options.bats | 2 +- test/bats/3-deploy-with-webhook.bats | 2 +- test/bats/test_helper.bash | 4 + .../news/first-post/featured-sunset-get.png | Bin 387442 -> 0 bytes .../content/en/blog/news/first-post/index.md | 46 - website/content/en/blog/news/release-0-8-1.md | 7 + website/content/en/blog/news/second-post.md | 245 - website/content/en/blog/releases/_index.md | 8 - .../in-depth-monoliths-detailed-spec.md | 245 - website/layouts/partials/navbar.html | 3 + website/package-lock.json | 14 +- 182 files changed, 31 insertions(+), 165174 deletions(-) delete mode 100644 docs/404.html delete mode 100644 docs/about/index.html delete mode 100644 docs/about/index.xml delete mode 100644 docs/blog/2018/01/04/another-great-release/index.html delete mode 100644 docs/blog/2018/10/06/easy-documentation-with-docsy/featured-sunset-get.png delete mode 100644 docs/blog/2018/10/06/easy-documentation-with-docsy/featured-sunset-get_hu69849a7cdb847c2393a7b3a7f6061c86_387442_250x125_fill_catmullrom_smart1_2.png delete mode 100644 docs/blog/2018/10/06/easy-documentation-with-docsy/featured-sunset-get_hu69849a7cdb847c2393a7b3a7f6061c86_387442_600x300_fill_catmullrom_smart1_2.png delete mode 100644 docs/blog/2018/10/06/easy-documentation-with-docsy/featured-sunset-get_hu69849a7cdb847c2393a7b3a7f6061c86_387442_640x0_resize_catmullrom_2.png delete mode 100644 docs/blog/2018/10/06/easy-documentation-with-docsy/index.html delete mode 100644 docs/blog/2018/10/06/the-second-blog-post/index.html delete mode 100644 docs/blog/index.html delete mode 100644 docs/blog/index.xml delete mode 100644 docs/blog/news/index.html delete mode 100644 docs/blog/news/index.xml delete mode 100644 docs/blog/news/page/1/index.html delete mode 100644 docs/blog/page/1/index.html delete mode 100644 docs/blog/releases/index.html delete mode 100644 docs/blog/releases/index.xml delete mode 100644 docs/blog/releases/page/1/index.html delete mode 100644 docs/community/index.html delete mode 100644 docs/community/index.xml delete mode 100644 docs/docs/developer-guide/index.html delete mode 100644 docs/docs/developer-guide/index.xml delete mode 100644 docs/docs/faq/index.html delete mode 100644 docs/docs/faq/index.xml delete mode 100644 docs/docs/getting-started/index.html delete mode 100644 docs/docs/getting-started/index.xml delete mode 100644 docs/docs/getting-started/latest/aks/index.html delete mode 100644 docs/docs/getting-started/latest/configuring-backup-and-restore/index.html delete mode 100644 docs/docs/getting-started/latest/configuring-seed-jobs-and-pipelines/index.html delete mode 100644 docs/docs/getting-started/latest/custom-backup-and-restore/index.html delete mode 100644 docs/docs/getting-started/latest/customizing-jenkins/index.html delete mode 100644 docs/docs/getting-started/latest/deploying-jenkins/index.html delete mode 100644 docs/docs/getting-started/latest/index.html delete mode 100644 docs/docs/getting-started/latest/index.xml delete mode 100644 docs/docs/getting-started/latest/installing-the-operator/index.html delete mode 100644 docs/docs/getting-started/latest/ldap/index.html delete mode 100644 docs/docs/getting-started/latest/openshift/index.html delete mode 100644 docs/docs/getting-started/latest/schema/index.html delete mode 100644 docs/docs/getting-started/latest/separate-namespaces/index.html delete mode 100644 docs/docs/getting-started/v0.1.x/aks/index.html delete mode 100644 docs/docs/getting-started/v0.1.x/configuration/index.html delete mode 100644 docs/docs/getting-started/v0.1.x/configure-backup-and-restore/index.html delete mode 100644 docs/docs/getting-started/v0.1.x/customization/index.html delete mode 100644 docs/docs/getting-started/v0.1.x/deploy-jenkins/index.html delete mode 100644 docs/docs/getting-started/v0.1.x/diagnostics/index.html delete mode 100644 docs/docs/getting-started/v0.1.x/index.html delete mode 100644 docs/docs/getting-started/v0.1.x/index.xml delete mode 100644 docs/docs/getting-started/v0.1.x/migration-guide-v1alpha1-to-v1alpha2/index.html delete mode 100644 docs/docs/getting-started/v0.1.x/scheme/index.html delete mode 100644 docs/docs/getting-started/v0.2.x/aks/index.html delete mode 100644 docs/docs/getting-started/v0.2.x/configuration/index.html delete mode 100644 docs/docs/getting-started/v0.2.x/configure-backup-and-restore/index.html delete mode 100644 docs/docs/getting-started/v0.2.x/custom-backup-and-restore/index.html delete mode 100644 docs/docs/getting-started/v0.2.x/customization/index.html delete mode 100644 docs/docs/getting-started/v0.2.x/deploy-jenkins/index.html delete mode 100644 docs/docs/getting-started/v0.2.x/diagnostics/index.html delete mode 100644 docs/docs/getting-started/v0.2.x/index.html delete mode 100644 docs/docs/getting-started/v0.2.x/index.xml delete mode 100644 docs/docs/getting-started/v0.2.x/migration/index.html delete mode 100644 docs/docs/getting-started/v0.2.x/schema/index.html delete mode 100644 docs/docs/getting-started/v0.3.x/aks/index.html delete mode 100644 docs/docs/getting-started/v0.3.x/configuration/index.html delete mode 100644 docs/docs/getting-started/v0.3.x/configure-backup-and-restore/index.html delete mode 100644 docs/docs/getting-started/v0.3.x/custom-backup-and-restore/index.html delete mode 100644 docs/docs/getting-started/v0.3.x/customization/index.html delete mode 100644 docs/docs/getting-started/v0.3.x/deploy-jenkins/index.html delete mode 100644 docs/docs/getting-started/v0.3.x/diagnostics/index.html delete mode 100644 docs/docs/getting-started/v0.3.x/index.html delete mode 100644 docs/docs/getting-started/v0.3.x/index.xml delete mode 100644 docs/docs/getting-started/v0.3.x/migration/index.html delete mode 100644 docs/docs/getting-started/v0.3.x/notifications/index.html delete mode 100644 docs/docs/getting-started/v0.3.x/schema/index.html delete mode 100644 docs/docs/getting-started/v0.4.x/aks/index.html delete mode 100644 docs/docs/getting-started/v0.4.x/configuration/index.html delete mode 100644 docs/docs/getting-started/v0.4.x/configure-backup-and-restore/index.html delete mode 100644 docs/docs/getting-started/v0.4.x/custom-backup-and-restore/index.html delete mode 100644 docs/docs/getting-started/v0.4.x/customization/index.html delete mode 100644 docs/docs/getting-started/v0.4.x/deploy-jenkins/index.html delete mode 100644 docs/docs/getting-started/v0.4.x/diagnostics/index.html delete mode 100644 docs/docs/getting-started/v0.4.x/index.html delete mode 100644 docs/docs/getting-started/v0.4.x/index.xml delete mode 100644 docs/docs/getting-started/v0.4.x/notifications/index.html delete mode 100644 docs/docs/getting-started/v0.4.x/openshift/index.html delete mode 100644 docs/docs/getting-started/v0.4.x/schema/index.html delete mode 100644 docs/docs/getting-started/v0.5.x/aks/index.html delete mode 100644 docs/docs/getting-started/v0.5.x/configuration/index.html delete mode 100644 docs/docs/getting-started/v0.5.x/configure-backup-and-restore/index.html delete mode 100644 docs/docs/getting-started/v0.5.x/custom-backup-and-restore/index.html delete mode 100644 docs/docs/getting-started/v0.5.x/customization/index.html delete mode 100644 docs/docs/getting-started/v0.5.x/deploy-jenkins/index.html delete mode 100644 docs/docs/getting-started/v0.5.x/diagnostics/index.html delete mode 100644 docs/docs/getting-started/v0.5.x/index.html delete mode 100644 docs/docs/getting-started/v0.5.x/index.xml delete mode 100644 docs/docs/getting-started/v0.5.x/notifications/index.html delete mode 100644 docs/docs/getting-started/v0.5.x/openshift/index.html delete mode 100644 docs/docs/getting-started/v0.5.x/schema/index.html delete mode 100644 docs/docs/getting-started/v0.6.x/aks/index.html delete mode 100644 docs/docs/getting-started/v0.6.x/configuring-backup-and-restore/index.html delete mode 100644 docs/docs/getting-started/v0.6.x/configuring-seed-jobs-and-pipelines/index.html delete mode 100644 docs/docs/getting-started/v0.6.x/custom-backup-and-restore/index.html delete mode 100644 docs/docs/getting-started/v0.6.x/customizing-jenkins/index.html delete mode 100644 docs/docs/getting-started/v0.6.x/deploying-jenkins/index.html delete mode 100644 docs/docs/getting-started/v0.6.x/index.html delete mode 100644 docs/docs/getting-started/v0.6.x/index.xml delete mode 100644 docs/docs/getting-started/v0.6.x/installing-the-operator/index.html delete mode 100644 docs/docs/getting-started/v0.6.x/ldap/index.html delete mode 100644 docs/docs/getting-started/v0.6.x/openshift/index.html delete mode 100644 docs/docs/getting-started/v0.6.x/schema/index.html delete mode 100644 docs/docs/getting-started/v0.6.x/separate-namespaces/index.html delete mode 100644 docs/docs/how-it-works/architecture-and-design/index.html delete mode 100644 docs/docs/how-it-works/index.html delete mode 100644 docs/docs/how-it-works/index.xml delete mode 100644 docs/docs/how-it-works/jenkins-docker-images/index.html delete mode 100644 docs/docs/index.html delete mode 100644 docs/docs/index.xml delete mode 100644 docs/docs/security/index.html delete mode 100644 docs/docs/security/index.xml delete mode 100644 docs/docs/troubleshooting/index.html delete mode 100644 docs/docs/troubleshooting/index.xml delete mode 100755 docs/favicons/android-144x144.png delete mode 100755 docs/favicons/android-192x192.png delete mode 100755 docs/favicons/android-36x36.png delete mode 100755 docs/favicons/android-48x48.png delete mode 100755 docs/favicons/android-72x72.png delete mode 100755 docs/favicons/android-96x196.png delete mode 100644 docs/favicons/apple-touch-icon-180x180.png delete mode 100644 docs/favicons/favicon-1024.png delete mode 100644 docs/favicons/favicon-16x16.png delete mode 100644 docs/favicons/favicon-256.png delete mode 100644 docs/favicons/favicon-32x32.png delete mode 100644 docs/favicons/favicon.ico delete mode 100644 docs/favicons/pwa-192x192.png delete mode 100644 docs/favicons/pwa-512x512.png delete mode 100644 docs/favicons/tile150x150.png delete mode 100644 docs/favicons/tile310x150.png delete mode 100644 docs/favicons/tile310x310.png delete mode 100644 docs/favicons/tile70x70.png delete mode 100644 docs/img/jenkins-credentials.png delete mode 100644 docs/img/jenkins-seed.png delete mode 100644 docs/img/jenkins.png delete mode 100644 docs/img/logo.svg delete mode 100644 docs/img/phases.png delete mode 100644 docs/img/reconcile.png delete mode 100644 docs/index.html delete mode 100644 docs/index.xml delete mode 100644 docs/js/main.min.b0e99aac17991fa76812dd47914049168ac469a1faa0939560f0b370565becab.js delete mode 100644 docs/robots.txt delete mode 100644 docs/scss/main.min.6c58fba96b88d035ce071c346f084a9dc4dedee4e80eb3724fe530520541a4ec.css delete mode 100644 docs/search/index.html delete mode 100644 docs/sitemap.xml delete mode 100644 docs/webfonts/fa-brands-400.eot delete mode 100644 docs/webfonts/fa-brands-400.svg delete mode 100644 docs/webfonts/fa-brands-400.ttf delete mode 100644 docs/webfonts/fa-brands-400.woff delete mode 100644 docs/webfonts/fa-brands-400.woff2 delete mode 100644 docs/webfonts/fa-regular-400.eot delete mode 100644 docs/webfonts/fa-regular-400.svg delete mode 100644 docs/webfonts/fa-regular-400.ttf delete mode 100644 docs/webfonts/fa-regular-400.woff delete mode 100644 docs/webfonts/fa-regular-400.woff2 delete mode 100644 docs/webfonts/fa-solid-900.eot delete mode 100644 docs/webfonts/fa-solid-900.svg delete mode 100644 docs/webfonts/fa-solid-900.ttf delete mode 100644 docs/webfonts/fa-solid-900.woff delete mode 100644 docs/webfonts/fa-solid-900.woff2 delete mode 100644 website/content/en/blog/news/first-post/featured-sunset-get.png delete mode 100644 website/content/en/blog/news/first-post/index.md create mode 100644 website/content/en/blog/news/release-0-8-1.md delete mode 100755 website/content/en/blog/news/second-post.md delete mode 100644 website/content/en/blog/releases/_index.md delete mode 100755 website/content/en/blog/releases/in-depth-monoliths-detailed-spec.md diff --git a/VERSION.txt b/VERSION.txt index b19b5211..85f7059b 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -v0.8.0 +v0.8.1 diff --git a/chart/jenkins-operator/README.md b/chart/jenkins-operator/README.md index e61ec2f6..04672664 100644 --- a/chart/jenkins-operator/README.md +++ b/chart/jenkins-operator/README.md @@ -98,7 +98,7 @@ Kubernetes native operator which fully manages Jenkins on Kubernetes | jenkins.volumes[0].persistentVolumeClaim.claimName | string | `"jenkins-backup"` | | | operator.affinity | object | `{}` | | | operator.fullnameOverride | string | `""` | | -| operator.image | string | `"quay.io/jenkins-kubernetes-operator/operator:v0.8.0"` | | +| operator.image | string | `"quay.io/jenkins-kubernetes-operator/operator:v0.8.1"` | | | operator.imagePullPolicy | string | `"IfNotPresent"` | | | operator.imagePullSecrets | list | `[]` | | | operator.nameOverride | string | `""` | | diff --git a/chart/jenkins-operator/values.yaml b/chart/jenkins-operator/values.yaml index 2ecdb579..a17dc884 100644 --- a/chart/jenkins-operator/values.yaml +++ b/chart/jenkins-operator/values.yaml @@ -312,7 +312,7 @@ operator: replicaCount: 1 # image is the name (and tag) of the Jenkins Operator image - image: quay.io/jenkins-kubernetes-operator/operator:v0.8.0 + image: quay.io/jenkins-kubernetes-operator/operator:v0.8.1 # imagePullPolicy defines policy for pulling images imagePullPolicy: IfNotPresent diff --git a/docs/404.html b/docs/404.html deleted file mode 100644 index 6e381325..00000000 --- a/docs/404.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - -404 Page not found | Jenkins Operator - - - - - - - - - - - - - - - - - - - - - -
- - - -
-
-
- -
-
-

Not found

-

Oops! This page doesn't exist. Try going back to our home page.

- -

You can learn how to make a 404 page like this in Custom 404 Pages.

-
-
- -
- -
-
-
-
- -
-
- -
-
- © 2023 Jenkins Operator is created by VirtusLab. Source is available under Apache License Version 2 and website content under Creative Commons Attribution-ShareAlike 4.0.
- Jenkins® is a registered trademark of Software in the Public Interest, Inc. - -

What's the Jenkins Operator?

- -
-
-
-
- - -
- - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/about/index.html b/docs/about/index.html deleted file mode 100644 index 6ee76b52..00000000 --- a/docs/about/index.html +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - -What's the Jenkins Operator? | Jenkins Operator - - - - - - - - - - - - - - - - - - - - - -
- - - -
-
-
- - - - - - - - -
-
- -
-Jenkins Operator is a Kubernetes native operator which fully manages Jenkins on Kubernetes.
It was built with immutability and declarative configuration as code in mind. -
-
-
-
- - - - - - - - -
-
-
- -
- Logo -
-
-

The main reason why we decided to develop the Jenkins Operator is the fact that we faced a lot of problems with standard Jenkins deployment. We want to make Jenkins more robust, suitable for dynamic and multi-tenant environments.

-

-
- Some of the problems we want to solve: -

    -
  • Installing plugins with incompatible versions or security vulnerabilities
  • -
  • Better configuration as code
  • -
  • Lack of end to end tests
  • -
  • Handling graceful shutdown properly
  • -
  • Security and hardening out of the box
  • -
  • Orphaned jobs with no jnlp connection
  • -
  • Making errors more visible for end users
  • -
-

- -
-
-
-
- - - -
- -
-
-
-
- -
-
- -
-
- © 2023 Jenkins Operator is created by VirtusLab. Source is available under Apache License Version 2 and website content under Creative Commons Attribution-ShareAlike 4.0.
- Jenkins® is a registered trademark of Software in the Public Interest, Inc. - -

What's the Jenkins Operator?

- -
-
-
-
- - -
- - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/about/index.xml b/docs/about/index.xml deleted file mode 100644 index 5073abfa..00000000 --- a/docs/about/index.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - Jenkins Operator – What's the Jenkins Operator? - https://jenkinsci.github.io/kubernetes-operator/about/ - Recent Hugo news from gohugo.io - Hugo -- gohugo.io - - https://jenkinsci.github.io/kubernetes-operator/img/hugo.png - GoHugo.io - https://jenkinsci.github.io/kubernetes-operator/about/ - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/blog/2018/01/04/another-great-release/index.html b/docs/blog/2018/01/04/another-great-release/index.html deleted file mode 100644 index cb1ea008..00000000 --- a/docs/blog/2018/01/04/another-great-release/index.html +++ /dev/null @@ -1,684 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - -Another Great Release | Jenkins Operator - - - - - - - - - - - - - - - - - - - - - - - - - - - - Another Great Release | Jenkins Operator - - -
- - - -
-
-
-
-
- - - - - -
- - - - -
- - - - -
- -
- - RSS - - -
-

Another Great Release

-
A short lead description about this content page. Text here can also be bold or italic and can even be split over multiple paragraphs.
- - - -

Text can be bold, italic, or strikethrough. Links should be blue with no underlines (unless hovered over).

- -

There should be whitespace between paragraphs. There should be whitespace between paragraphs. There should be whitespace between paragraphs. There should be whitespace between paragraphs.

- -

There should be whitespace between paragraphs. There should be whitespace between paragraphs. There should be whitespace between paragraphs. There should be whitespace between paragraphs.

- -
-

There should be no margin above this first sentence.

- -

Blockquotes should be a lighter gray with a border along the left side in the secondary color.

- -

There should be no margin below this final sentence.

-
- -

First Header

- -

This is a normal paragraph following a header. Bacon ipsum dolor sit amet t-bone doner shank drumstick, pork belly porchetta chuck sausage brisket ham hock rump pig. Chuck kielbasa leberkas, pork bresaola ham hock filet mignon cow shoulder short ribs biltong. Bacon ipsum dolor sit amet t-bone doner shank drumstick, pork belly porchetta chuck sausage brisket ham hock rump pig. Chuck kielbasa leberkas, pork bresaola ham hock filet mignon cow shoulder short ribs biltong. Bacon ipsum dolor sit amet t-bone doner shank drumstick, pork belly porchetta chuck sausage brisket ham hock rump pig. Chuck kielbasa leberkas, pork bresaola ham hock filet mignon cow shoulder short ribs biltong.

- -

Bacon ipsum dolor sit amet t-bone doner shank drumstick, pork belly porchetta chuck sausage brisket ham hock rump pig. Chuck kielbasa leberkas, pork bresaola ham hock filet mignon cow shoulder short ribs biltong.

- -

On big screens, paragraphs and headings should not take up the full container width, but we want tables, code blocks and similar to take the full width.

- -

Lorem markdownum tuta hospes stabat; idem saxum facit quaterque repetito -occumbere, oves novem gestit haerebat frena; qui. Respicit recurvam erat: -pignora hinc reppulit nos aut, aptos, ipsa.

- -

Meae optatos passa est Epiros utiliter Talibus niveis, hoc lata, edidit. -Dixi ad aestum.

- -

Header 2

- -
-

This is a blockquote following a header. Bacon ipsum dolor sit amet t-bone doner shank drumstick, pork belly porchetta chuck sausage brisket ham hock rump pig. Chuck kielbasa leberkas, pork bresaola ham hock filet mignon cow shoulder short ribs biltong.

-
- -

Header 3

-
This is a code block following a header.
-

Header 4

- -
    -
  • This is an unordered list following a header.
  • -
  • This is an unordered list following a header.
  • -
  • This is an unordered list following a header.
  • -
- -
Header 5
- -
    -
  1. This is an ordered list following a header.
  2. -
  3. This is an ordered list following a header.
  4. -
  5. This is an ordered list following a header.
  6. -
- -
Header 6
- - - - - - - - - - - - - - - - - - - - - - - - - -
WhatFollows
A tableA header
A tableA header
A tableA header
- -
- -

There’s a horizontal rule above and below this.

- -
- -

Here is an unordered list:

- -
    -
  • Salt-n-Pepa
  • -
  • Bel Biv DeVoe
  • -
  • Kid ‘N Play
  • -
- -

And an ordered list:

- -
    -
  1. Michael Jackson
  2. -
  3. Michael Bolton
  4. -
  5. Michael Bublé
  6. -
- -

And an unordered task list:

- -
    -
  • -
  • -
  • -
- -

And a “mixed” task list:

- -
    -
  • -
  • ?
  • -
  • -
- -

And a nested list:

- -
    -
  • Jackson 5 - -
      -
    • Michael
    • -
    • Tito
    • -
    • Jackie
    • -
    • Marlon
    • -
    • Jermaine
    • -
  • -
  • TMNT - -
      -
    • Leonardo
    • -
    • Michelangelo
    • -
    • Donatello
    • -
    • Raphael
    • -
  • -
- -

Definition lists can be used with Markdown syntax. Definition terms are bold.

- -
-
Name
-
Godzilla
-
Born
-
1952
-
Birthplace
-
Japan
-
Color
-
Green
-
- -
- -

Tables should have bold headings and alternating shaded rows.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ArtistAlbumYear
Michael JacksonThriller1982
PrincePurple Rain1984
Beastie BoysLicense to Ill1986
- -

If a table is too wide, it should scroll horizontally.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ArtistAlbumYearLabelAwardsSongs
Michael JacksonThriller1982Epic RecordsGrammy Award for Album of the Year, American Music Award for Favorite Pop/Rock Album, American Music Award for Favorite Soul/R&B Album, Brit Award for Best Selling Album, Grammy Award for Best Engineered Album, Non-ClassicalWanna Be Startin’ Somethin’, Baby Be Mine, The Girl Is Mine, Thriller, Beat It, Billie Jean, Human Nature, P.Y.T. (Pretty Young Thing), The Lady in My Life
PrincePurple Rain1984Warner Brothers RecordsGrammy Award for Best Score Soundtrack for Visual Media, American Music Award for Favorite Pop/Rock Album, American Music Award for Favorite Soul/R&B Album, Brit Award for Best Soundtrack/Cast Recording, Grammy Award for Best Rock Performance by a Duo or Group with VocalLet’s Go Crazy, Take Me With U, The Beautiful Ones, Computer Blue, Darling Nikki, When Doves Cry, I Would Die 4 U, Baby I’m a Star, Purple Rain
Beastie BoysLicense to Ill1986Mercury RecordsnoawardsbutthistablecelliswideRhymin & Stealin, The New Style, She’s Crafty, Posse in Effect, Slow Ride, Girls, (You Gotta) Fight for Your Right, No Sleep Till Brooklyn, Paul Revere, Hold It Now, Hit It, Brass Monkey, Slow and Low, Time to Get Ill
- -
- -

Code snippets like var foo = "bar"; can be shown inline.

- -

Also, this should vertically align with this and this.

- -

Code can also be shown in a block element.

-
foo := "bar";
-bar := "foo";
-

Code can also use syntax highlighting.

-
func main() {
-  input := `var foo = "bar";`
-
-  lexer := lexers.Get("javascript")
-  iterator, _ := lexer.Tokenise(nil, input)
-  style := styles.Get("github")
-  formatter := html.New(html.WithLineNumbers())
-
-  var buff bytes.Buffer
-  formatter.Format(&buff, style, iterator)
-
-  fmt.Println(buff.String())
-}
Long, single-line code blocks should not wrap. They should horizontally scroll if they are too long. This line should be long enough to demonstrate this.
-

Inline code inside table cells should still be distinguishable.

- - - - - - - - - - - - - - - - - - - - -
LanguageCode
Javascriptvar foo = "bar";
Rubyfoo = "bar"{
- -
- -

Small images should be shown at their actual size.

- -

- -

Large images should always scale down and fit in the content container.

- -

- -

Components

- -

Alerts

- -

- -

- - - - - - - - - - - - - - - - -

- -

Sizing

- -

Add some sections here to see how the ToC looks like. Bacon ipsum dolor sit amet t-bone doner shank drumstick, pork belly porchetta chuck sausage brisket ham hock rump pig. Chuck kielbasa leberkas, pork bresaola ham hock filet mignon cow shoulder short ribs biltong.

- -

Parameters available

- -

Bacon ipsum dolor sit amet t-bone doner shank drumstick, pork belly porchetta chuck sausage brisket ham hock rump pig. Chuck kielbasa leberkas, pork bresaola ham hock filet mignon cow shoulder short ribs biltong.

- -

Using pixels

- -

Bacon ipsum dolor sit amet t-bone doner shank drumstick, pork belly porchetta chuck sausage brisket ham hock rump pig. Chuck kielbasa leberkas, pork bresaola ham hock filet mignon cow shoulder short ribs biltong.

- -

Using rem

- -

Bacon ipsum dolor sit amet t-bone doner shank drumstick, pork belly porchetta chuck sausage brisket ham hock rump pig. Chuck kielbasa leberkas, pork bresaola ham hock filet mignon cow shoulder short ribs biltong.

- -

Memory

- -

Bacon ipsum dolor sit amet t-bone doner shank drumstick, pork belly porchetta chuck sausage brisket ham hock rump pig. Chuck kielbasa leberkas, pork bresaola ham hock filet mignon cow shoulder short ribs biltong.

- -

RAM to use

- -

Bacon ipsum dolor sit amet t-bone doner shank drumstick, pork belly porchetta chuck sausage brisket ham hock rump pig. Chuck kielbasa leberkas, pork bresaola ham hock filet mignon cow shoulder short ribs biltong.

- -

More is better

- -

Bacon ipsum dolor sit amet t-bone doner shank drumstick, pork belly porchetta chuck sausage brisket ham hock rump pig. Chuck kielbasa leberkas, pork bresaola ham hock filet mignon cow shoulder short ribs biltong.

- -

Used RAM

- -

Bacon ipsum dolor sit amet t-bone doner shank drumstick, pork belly porchetta chuck sausage brisket ham hock rump pig. Chuck kielbasa leberkas, pork bresaola ham hock filet mignon cow shoulder short ribs biltong.

-
This is the final element on the page and there should be no margin below this.
- - - - -
- - -
-
-
- -
-
-
-
- -
-
- -
-
- © 2023 Jenkins Operator is created by VirtusLab. Source is available under Apache License Version 2 and website content under Creative Commons Attribution-ShareAlike 4.0.
- Jenkins® is a registered trademark of Software in the Public Interest, Inc. - -

What's the Jenkins Operator?

- -
-
-
-
- - -
- - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/blog/2018/10/06/easy-documentation-with-docsy/featured-sunset-get.png b/docs/blog/2018/10/06/easy-documentation-with-docsy/featured-sunset-get.png deleted file mode 100644 index db3373c0bfb39ce0d6707ac730766cf00c27f2de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 387442 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