- path: test/utils/image/manifest.go
match: configs\[Etcd\] = Config{list\.GcEtcdRegistry, "etcd", "\d+\.\d+.\d+(-(alpha|beta|rc).\d+)?(-\d+)?"}
- - name: "etcd-image"
- version: 3.6.4
- refPaths:
- - path: cluster/images/etcd/Makefile
- match: BUNDLED_ETCD_VERSIONS\?|LATEST_ETCD_VERSION\?
- - path: cluster/images/etcd/migrate/options.go
-
- name: "node-problem-detector"
version: 1.34.0
refPaths:
#- path: cluster/gce/windows/k8s-node-setup.psm1
# match: DEFAULT_NPD_VERSION
- # From https://github.com/etcd-io/etcd/blob/main/Makefile
- - name: "golang: etcd release version"
- version: 1.23.11 # https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.6.md
- refPaths:
- - path: cluster/images/etcd/Makefile
- match: 'GOLANG_VERSION := \d+.\d+(alpha|beta|rc)?\.?(\d+)?'
-
# Golang
# TODO: this should really be eliminated and controlled by .go-version
- name: "golang: upstream version"
- name: "registry.k8s.io/debian-base: dependents"
version: bookworm-v1.0.6
refPaths:
- - path: cluster/images/etcd/Makefile
- match: BASEIMAGE\?\=registry\.k8s\.io\/build-image\/debian-base:[a-zA-Z]+\-v((([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)
- - path: cluster/images/etcd/Makefile
- match: BASEIMAGE\?\=registry\.k8s\.io\/build-image\/debian-base-arm:[a-zA-Z]+\-v((([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)
- - path: cluster/images/etcd/Makefile
- match: BASEIMAGE\?\=registry\.k8s\.io\/build-image\/debian-base-arm64:[a-zA-Z]+\-v((([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)
- - path: cluster/images/etcd/Makefile
- match: BASEIMAGE\?\=registry\.k8s\.io\/build-image\/debian-base-ppc64le:[a-zA-Z]+\-v((([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)
- - path: cluster/images/etcd/Makefile
- match: BASEIMAGE\?\=registry\.k8s\.io\/build-image\/debian-base-s390x:[a-zA-Z]+\-v((([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)
- path: test/conformance/image/Makefile
match: BASE_IMAGE_VERSION\?=
- path: test/images/pets/peer-finder/BASEIMAGE
refPaths:
- path: build/pause/cloudbuild.yaml
match: gcr.io/k8s-staging-test-infra/gcb-docker-gcloud
- - path: cluster/images/etcd/cloudbuild.yaml
- match: gcr.io/k8s-staging-test-infra/gcb-docker-gcloud
- path: test/images/cloudbuild.yaml
match: gcr.io/k8s-staging-test-infra/gcb-docker-gcloud
+++ /dev/null
-# Copyright 2016 The Kubernetes Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-ARG BASEIMAGE
-ARG RUNNERIMAGE
-
-FROM ${BASEIMAGE} as builder
-
-# This image needs bash for running "migrate-if-needed.sh". Instead of a full debian image
-# we use just the bash-static and we wrap bash-static into a distroless image instead of
-# a full debian image
-RUN apt-get update -y \
- && apt-get -yy -q install --no-install-recommends --no-install-suggests --fix-missing \
- bash-static
-
-RUN cp /bin/bash-static /sh
-
-FROM ${RUNNERIMAGE}
-WORKDIR /
-
-COPY --from=builder /sh /bin/
-
-EXPOSE 2379 2380 4001 7001
-# etcdctl is used by etcd.manifest for livenessProbe.
-COPY etcd* etcdctl* /usr/local/bin/
-COPY migrate-if-needed.sh migrate /usr/local/bin/
+++ /dev/null
-# Copyright 2021 The Kubernetes Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-ARG RUNNERIMAGE
-FROM ${RUNNERIMAGE}
-
-EXPOSE 2379 2380 4001 7001
-
-WORKDIR C:/usr/local/bin
-COPY etcd* etcdctl* /usr/local/bin/
-COPY migrate-if-needed.bat /usr/local/bin/
-COPY migrate /usr/local/bin/migrate.exe
-
-# NOTE(claudiub): docker buildx sets the PATH env variable to a Linux-like PATH,
-# # which is not desirable. See: https://github.com/moby/buildkit/issues/1560
-# # TODO(claudiub): remove this once the issue has been resolved.
-ENV PATH="C:\usr\local\bin;C:\Windows\system32;C:\Windows;"
+++ /dev/null
-# Copyright 2016 The Kubernetes Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Build the etcd image
-#
-# Usage:
-# [BUNDLED_ETCD_VERSIONS=3.4.18 3.5.21 3.6.4] [REGISTRY=registry.k8s.io] [ARCH=amd64] [BASEIMAGE=busybox] make (build|push)
-#
-# The image contains different etcd versions to simplify
-# upgrades. Thus be careful when removing any versions from here.
-#
-# NOTE: The etcd upgrade rules are that you can upgrade only 1 minor
-# version at a time, and patch release don't matter.
-#
-# Except from etcd-$(version) and etcdctl-$(version) binaries, we also
-# need etcd and etcdctl binaries for backward compatibility reasons.
-# That binary will be set to the last version from $(BUNDLED_ETCD_VERSIONS).
-BUNDLED_ETCD_VERSIONS?=3.4.18 3.5.21 3.6.4
-
-# LATEST_ETCD_VERSION identifies the most recent etcd version available.
-LATEST_ETCD_VERSION?=3.6.4
-
-# REVISION provides a version number for this image and all it's bundled
-# artifacts. It should start at zero for each LATEST_ETCD_VERSION and increment
-# for each revision of this image at that etcd version.
-REVISION?=0
-
-# IMAGE_TAG Uniquely identifies registry.k8s.io/etcd docker image with a tag of the form "<etcd-version>-<revision>".
-IMAGE_TAG=$(LATEST_ETCD_VERSION)-$(REVISION)
-
-ARCH?=amd64
-
-# Operating systems supported: linux, windows
-OS ?= linux
-# OS Version for the Windows images: 1809, ltsc2022
-OSVERSION ?= 1809
-
-# The output type could either be docker (local), or registry.
-# If it is registry, it will also allow us to push the Windows images.
-OUTPUT_TYPE ?= docker
-
-ALL_OS = linux windows
-ALL_ARCH.linux = amd64 arm arm64 ppc64le s390x
-ALL_OS_ARCH.linux = $(foreach arch, ${ALL_ARCH.linux}, linux-$(arch))
-ALL_ARCH.windows = amd64
-ALL_OSVERSIONS.windows := 1809 ltsc2022
-ALL_OS_ARCH.windows = $(foreach arch, $(ALL_ARCH.windows), $(foreach osversion, ${ALL_OSVERSIONS.windows}, windows-$(arch)-${osversion}))
-ALL_OS_ARCH = $(foreach os, $(ALL_OS), ${ALL_OS_ARCH.${os}})
-
-IMAGE_SUFFIX.linux = $(OS)-$(ARCH)
-IMAGE_SUFFIX.windows = $(OS)-$(ARCH)-$(OSVERSION)
-IMAGE_SUFFIX := ${IMAGE_SUFFIX.${OS}}
-
-# Image should be pulled from registry.k8s.io, which will auto-detect
-# region (us, eu, asia, ...) and pull from the closest.
-REGISTRY?=registry.k8s.io
-# Images should be pushed to staging-k8s.gcr.io.
-PUSH_REGISTRY?=staging-k8s.gcr.io
-
-MANIFEST_IMAGE := $(PUSH_REGISTRY)/etcd
-
-# Install binaries matching base distro permissions
-BIN_INSTALL := install -m 0555
-
-# Hosts running SELinux need :z added to volume mounts
-SELINUX_ENABLED := $(shell cat /sys/fs/selinux/enforce 2> /dev/null || echo 0)
-
-ifeq ($(SELINUX_ENABLED),1)
- DOCKER_VOL_OPTS?=:z
-endif
-
-# This option is for running docker manifest command
-export DOCKER_CLI_EXPERIMENTAL := enabled
-# golang version should match the golang version of the official build from https://github.com/etcd-io/etcd/releases.
-GOLANG_VERSION := 1.23.11 # https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.6.md
-GOARM?=7
-TEMP_DIR:=$(shell mktemp -d)
-
-DOCKERFILE.linux = Dockerfile
-DOCKERFILE.windows = Dockerfile.windows
-DOCKERFILE := ${DOCKERFILE.${OS}}
-
-ifeq ($(ARCH),amd64)
- BASEIMAGE?=registry.k8s.io/build-image/debian-base:bookworm-v1.0.6
-endif
-ifeq ($(ARCH),arm)
- BASEIMAGE?=registry.k8s.io/build-image/debian-base-arm:bookworm-v1.0.6
-endif
-ifeq ($(ARCH),arm64)
- BASEIMAGE?=registry.k8s.io/build-image/debian-base-arm64:bookworm-v1.0.6
-endif
-ifeq ($(ARCH),ppc64le)
- BASEIMAGE?=registry.k8s.io/build-image/debian-base-ppc64le:bookworm-v1.0.6
-endif
-ifeq ($(ARCH),s390x)
- BASEIMAGE?=registry.k8s.io/build-image/debian-base-s390x:bookworm-v1.0.6
-endif
-
-BASE.windows = mcr.microsoft.com/windows/nanoserver
-
-RUNNERIMAGE.windows?=$(BASE.windows):$(OSVERSION)
-RUNNERIMAGE.linux?=gcr.io/distroless/static:latest
-RUNNERIMAGE := ${RUNNERIMAGE.${OS}}
-
-QEMUVERSION?=5.2.0-2
-
-build:
- # Explicitly copy files to the temp directory
- $(BIN_INSTALL) migrate-if-needed.sh $(TEMP_DIR)
- $(BIN_INSTALL) migrate-if-needed.bat $(TEMP_DIR)
- install $(DOCKERFILE) $(TEMP_DIR)
-
- # Compile migrate
- migrate_tmp_dir=$(shell mktemp -d); \
- docker run --rm --interactive -v $(shell pwd)/../../../:/go/src/k8s.io/kubernetes$(DOCKER_VOL_OPTS) -v $${migrate_tmp_dir}:/build$(DOCKER_VOL_OPTS) -e GOOS=$(OS) -e GOARCH=$(ARCH) golang:$(GOLANG_VERSION) \
- /bin/bash -c "CGO_ENABLED=0 GO111MODULE=off go build -o /build/migrate k8s.io/kubernetes/cluster/images/etcd/migrate"; \
- $(BIN_INSTALL) $${migrate_tmp_dir}/migrate $(TEMP_DIR);
-
-ifeq ($(ARCH),amd64)
-
- # Do not compile if we should make an image for amd64, use the official etcd binaries instead
- # For each release create a tmp dir 'etcd_release_tmp_dir' and unpack the release tar there.
-ifeq ($(OS),windows)
- for version in $(BUNDLED_ETCD_VERSIONS); do \
- etcd_release_tmp_dir=$(shell mktemp -d); \
- curl -sSL --retry 5 https://github.com/etcd-io/etcd/releases/download/v$$version/etcd-v$$version-windows-amd64.zip -o etcd-v$$version-windows-amd64.zip; \
- unzip -q -d $$etcd_release_tmp_dir etcd-v$$version-windows-amd64.zip; \
- rm etcd-v$$version-windows-amd64.zip; \
- $(BIN_INSTALL) $$etcd_release_tmp_dir/etcd-v$$version-windows-amd64/etcd.exe $$etcd_release_tmp_dir/etcd-v$$version-windows-amd64/etcdctl.exe $(TEMP_DIR)/; \
- $(BIN_INSTALL) $(TEMP_DIR)/etcd.exe $(TEMP_DIR)/etcd-$$version.exe; \
- $(BIN_INSTALL) $(TEMP_DIR)/etcdctl.exe $(TEMP_DIR)/etcdctl-$$version.exe; \
- done
-else
- for version in $(BUNDLED_ETCD_VERSIONS); do \
- etcd_release_tmp_dir=$(shell mktemp -d); \
- curl -sSL --retry 5 https://github.com/etcd-io/etcd/releases/download/v$$version/etcd-v$$version-linux-amd64.tar.gz | tar -xz -C $$etcd_release_tmp_dir --strip-components=1; \
- $(BIN_INSTALL) $$etcd_release_tmp_dir/etcd $$etcd_release_tmp_dir/etcdctl $(TEMP_DIR)/; \
- $(BIN_INSTALL) $(TEMP_DIR)/etcd $(TEMP_DIR)/etcd-$$version; \
- $(BIN_INSTALL) $(TEMP_DIR)/etcdctl $(TEMP_DIR)/etcdctl-$$version; \
- done
-endif
-
-else
-
- # Download etcd in a golang container and cross-compile it statically
- # For each release create a tmp dir 'etcd_release_tmp_dir' and unpack the release tar there.
- arch_prefix=""
- ifeq ($(ARCH),arm)
- arch_prefix="GOARM=$(GOARM)"
- endif
-
- # use '/go/src/go.etcd.io/etcd' to build etcd 3.4 and later.
- for version in $(BUNDLED_ETCD_VERSIONS); do \
- etcd_release_tmp_dir=$(shell mktemp -d); \
- etcd_build_dir="/go/src/github.com/coreos/etcd"; \
- etcd_build_script="./build.sh"; \
- if [ $$(echo $$version | cut -d. -f2) -gt 3 ]; then \
- etcd_build_dir="/go/src/go.etcd.io/etcd"; \
- fi; \
- if [ $$(echo $$version | cut -d. -f2) -gt 5 ]; then \
- etcd_build_script="./scripts/build.sh"; \
- fi; \
- docker run --rm --interactive -v $${etcd_release_tmp_dir}:/etcdbin golang:$(GOLANG_VERSION)$(DOCKER_VOL_OPTS) /bin/bash -c \
- "git clone https://github.com/etcd-io/etcd $$etcd_build_dir \
- && cd $$etcd_build_dir \
- && git checkout v$${version} \
- && $(arch_prefix) GOARCH=$(ARCH) $$etcd_build_script \
- && cp -f bin/$(ARCH)/etcd* bin/etcd* /etcdbin; echo 'done'"; \
- $(BIN_INSTALL) $$etcd_release_tmp_dir/etcd $$etcd_release_tmp_dir/etcdctl $(TEMP_DIR)/; \
- $(BIN_INSTALL) $(TEMP_DIR)/etcd $(TEMP_DIR)/etcd-$$version; \
- $(BIN_INSTALL) $(TEMP_DIR)/etcdctl $(TEMP_DIR)/etcdctl-$$version; \
- done
-
- # Add this ENV variable in order to workaround an unsupported arch blocker
- # On arm (which is 32-bit), it can't handle >1GB data in-memory
- ifeq ($(ARCH),arm)
- cd $(TEMP_DIR) && echo "ENV ETCD_UNSUPPORTED_ARCH=$(ARCH)" >> $(DOCKERFILE)
- endif
-endif
-
- docker run --rm --privileged multiarch/qemu-user-static:$(QEMUVERSION) --reset -p yes
- docker buildx version
- BUILDER=$(shell docker buildx create --use)
-
- # And build the image
- docker buildx build \
- --pull \
- --provenance=false \
- --sbom=false \
- --output=type=$(OUTPUT_TYPE) \
- --platform "$(OS)/$(ARCH)" \
- -t $(REGISTRY)/etcd:$(IMAGE_TAG)-$(IMAGE_SUFFIX) \
- --build-arg BASEIMAGE=$(BASEIMAGE) \
- --build-arg RUNNERIMAGE=$(RUNNERIMAGE) \
- -f $(TEMP_DIR)/$(DOCKERFILE) \
- $(TEMP_DIR)
- docker buildx rm $$BUILDER
-
-push: build
-
-# split words on hyphen, access by 1-index
-word-hyphen = $(word $2,$(subst -, ,$1))
-
-sub-build-%:
- $(MAKE) OUTPUT_TYPE=docker OS=$(call word-hyphen,$*,1) ARCH=$(call word-hyphen,$*,2) build
-
-all-build: $(addprefix sub-build-,$(ALL_OS_ARCH))
-
-sub-push-image-%:
- $(MAKE) OUTPUT_TYPE=registry OS=$(call word-hyphen,$*,1) ARCH=$(call word-hyphen,$*,2) OSVERSION=$(call word-hyphen,$*,3) REGISTRY=$(PUSH_REGISTRY) push
-
-all-push-images: $(addprefix sub-push-image-,$(ALL_OS_ARCH))
-
-# NOTE(claudiub): A non-default builder instance is needed in order to build Windows images.
-all-push: all-push-images push-manifest
-
-push-manifest:
- docker manifest create --amend $(MANIFEST_IMAGE):$(IMAGE_TAG) $(shell echo $(ALL_OS_ARCH) | sed -e "s~[^ ]*~$(MANIFEST_IMAGE):$(IMAGE_TAG)\-&~g")
- set -x; for arch in $(ALL_ARCH.linux); do docker manifest annotate --os linux --arch $${arch} ${MANIFEST_IMAGE}:${IMAGE_TAG} ${MANIFEST_IMAGE}:${IMAGE_TAG}-linux-$${arch}; done
- # For Windows images, we also need to include the "os.version" in the manifest list, so the Windows node can pull the proper image it needs.
- # we use awk to also trim the quotes around the OS version string.
- set -x; \
- for arch in $(ALL_ARCH.windows); do \
- for osversion in ${ALL_OSVERSIONS.windows}; do \
- full_version=`docker manifest inspect ${BASE.windows}:$${osversion} | grep "os.version" | head -n 1 | awk -F\" '{print $$4}'` || true; \
- docker manifest annotate --os windows --arch $${arch} --os-version $${full_version} ${MANIFEST_IMAGE}:${IMAGE_TAG} ${MANIFEST_IMAGE}:${IMAGE_TAG}-windows-$${arch}-$${osversion}; \
- done; \
- done
- docker manifest push --purge ${MANIFEST_IMAGE}:${IMAGE_TAG}
-
-unit-test:
- docker run --rm --interactive -v $(shell pwd)/../../../:/go/src/k8s.io/kubernetes$(DOCKER_VOL_OPTS) -e GOARCH=$(ARCH) golang:$(GOLANG_VERSION) \
- /bin/bash -c "CGO_ENABLED=0 go test -v k8s.io/kubernetes/cluster/images/etcd/migrate"
-
-# Integration tests require both a golang build environment and all the etcd binaries from a `registry.k8s.io/etcd` image (`/usr/local/bin/etcd-<version>`, ...).
-# Since the `registry.k8s.io/etcd` image is for runtime only and does not have a build golang environment, we create a new docker image to run integration tests
-# with.
-build-integration-test-image: build
- cp -r $(TEMP_DIR) $(TEMP_DIR)_integration_test
- cp Dockerfile $(TEMP_DIR)_integration_test/Dockerfile
- docker build \
- --pull \
- -t etcd-integration-test \
- --build-arg BASEIMAGE=golang:$(GOLANG_VERSION) \
- --build-arg RUNNERIMAGE=$(RUNNERIMAGE) \
- $(TEMP_DIR)_integration_test
-
-integration-test:
- docker run --rm --interactive -v $(shell pwd)/../../../:/go/src/k8s.io/kubernetes$(DOCKER_VOL_OPTS) -e GOARCH=$(ARCH) etcd-integration-test \
- /bin/bash -c "CGO_ENABLED=0 go test -tags=integration k8s.io/kubernetes/cluster/images/etcd/migrate -args -v 10 -logtostderr true"
-
-integration-build-test: build-integration-test-image integration-test
-test: unit-test integration-build-test
-all: all-build test
-.PHONY: build push push-manifest all-push all-push-images all-build unit-test build-integration-test-image integration-test integration-build-test test
+++ /dev/null
-# See the OWNERS docs at https://go.k8s.io/owners
-
-reviewers:
- - jpbetz
- - serathius
-approvers:
- - jpbetz
- - serathius
-labels:
- - sig/etcd
+++ /dev/null
-### registry.k8s.io/etcd docker image
-
-Provides docker images containing etcd and etcdctl binaries for multiple etcd
-version as well as a migration operator utility for upgrading and downgrading
-etcd--it's data directory in particular--to a target version.
-
-#### Versioning
-
-Each `registry.k8s.io/etcd` docker image is tagged with an version string of the form
-`<etcd-version>-<image-revision>`, e.g. `3.0.17-0`. The etcd version is the
-SemVer of latest etcd version available in the image. The image revision
-distinguishes between docker images with the same lastest etcd version but
-changes (bug fixes and backward compatible improvements) to the migration
-utility bundled with the image.
-
-In addition to the latest etcd version, each `registry.k8s.io/etcd` image contains
-etcd and etcdctl binaries for older versions of etcd. These are used by the
-migration operator utility when performing downgrades and multi-step upgrades,
-but can also be used as the etcd target version.
-
-#### Usage
-
-Always run `/usr/local/bin/migrate` (or the
-`/usr/local/bin/migrate-if-needed.sh` wrapper script) before starting the etcd
-server. On Windows, run `C:\bin\migrate.exe` (or the `C:\bin\migrate-if-needed.bat
-wrapper script`).
-
-`migrate` writes a `version.txt` file to track the "current" version
-of etcd that was used to persist data to disk. A "target" version may also be provided
-by the `TARGET_STORAGE` (e.g. "etcd3") and `TARGET_VERSION` (e.g. "3.4.13" )
-environment variables. If the persisted version differs from the target version,
-`migrate-if-needed.sh` will migrate the data from the current to the target
-version.
-
-Upgrades to any target version are supported. The data will be automatically upgraded
-in steps to each minor version until the target version is reached.
-
-Downgrades to the previous minor version of the 3.x series is supported.
-
-#### Permissions
-
-By default, `migrate` will write data directory files with default permissions
-according to the umask it is run with. When run in the published
-`registry.k8s.io/etcd` images the default umask is 0022 which will result in 0755
-directory permissions and 0644 file permissions.
-
-#### Cross building
-
-For `amd64`, official `etcd` and `etcdctl` binaries are downloaded from Github
-to maintain official support. For other architectures, `etcd` is cross-compiled
-from source. Arch-specific `debian` images serve as base images.
-
-Windows images can be built on Linux nodes due to `docker buildx`, but they will
-only be created and pushed when using the `all-push` make target.
-
-#### How to release
-
-First, update `ETCD_VERSION` and `REVSION` in the `Makefile`.
-
-Next, build and test the image:
-
-```console
-$ make build test
-```
-
-Last, build and push the docker images for all supported architectures.
-
-```console
-# Build images for all the architecture and push the manifest image as well
-$ make all-push
-
-# Build images for all the architecture
-$ make all-build
-
-# Build image for target architecture(default=amd64)
-$ make build ARCH=ppc64le
-```
-
-If you don't want to push the images, run `make` or `make build` instead
+++ /dev/null
-# See https://cloud.google.com/cloud-build/docs/build-config
-timeout: 1200s
-options:
- substitution_option: ALLOW_LOOSE
- machineType: 'N1_HIGHCPU_8'
-steps:
- - name: 'gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:v20240523-a15ad90fc9@sha256:bb04162508c2c61637eae700a0d8e8c8be8f2d4c831d2b75e59db2d4dd6cf75d'
- entrypoint: 'bash'
- dir: ./cluster/images/etcd
- env:
- - DOCKER_CLI_EXPERIMENTAL=enabled
- - REGISTRY=gcr.io/$PROJECT_ID
- - PUSH_REGISTRY=gcr.io/$PROJECT_ID
- - IMAGE=gcr.io/$PROJECT_ID/etcd
- - BUILD_IMAGE=debian-build
- - TMPDIR=/workspace
- - HOME=/root # for docker buildx
- args:
- - '-c'
- - |
- gcloud auth configure-docker \
- && docker buildx create --name img-builder --use \
- && docker buildx inspect --bootstrap \
- && docker run --rm --privileged linuxkit/binfmt:4ea3b9b0938cbd19834c096aa31ff475cc75d281 \
- && make all-push
+++ /dev/null
-@echo off
-REM Copyright 2021 The Kubernetes Authors.
-REM
-REM Licensed under the Apache License, Version 2.0 (the "License");
-REM you may not use this file except in compliance with the License.
-REM You may obtain a copy of the License at
-REM
-REM http://www.apache.org/licenses/LICENSE-2.0
-REM
-REM Unless required by applicable law or agreed to in writing, software
-REM distributed under the License is distributed on an "AS IS" BASIS,
-REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-REM See the License for the specific language governing permissions and
-REM limitations under the License.
-
-REM DEPRECATED:
-REM The functionality has been moved to migrate binary and this script
-REM if left for backward compatibility with previous manifests. It will be
-REM removed in the future.
-
-C:\bin\migrate.exe
+++ /dev/null
-#!/bin/sh
-
-# Copyright 2016 The Kubernetes Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-# DEPRECATED:
-# The functionality has been moved to migrate binary and this script
-# if left for backward compatibility with previous manifests. It will be
-# removed in the future.
-
-set -o errexit
-set -o nounset
-
-/usr/local/bin/migrate
+++ /dev/null
-/*
-Copyright 2020 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package main
-
-import (
- "fmt"
- "io"
- "os"
- "path/filepath"
-)
-
-func copyFile(source, dest string) error {
- sf, err := os.Open(source)
- if err != nil {
- return fmt.Errorf("unable to open source file [%s]: %q", source, err)
- }
- defer sf.Close()
- fi, err := sf.Stat()
- if err != nil {
- return fmt.Errorf("unable to stat source file [%s]: %q", source, err)
- }
-
- dir := filepath.Dir(dest)
- if err := os.MkdirAll(dir, 0755); err != nil {
- return fmt.Errorf("unable to create directory [%s]: %q", dir, err)
- }
- df, err := os.Create(dest)
- if err != nil {
- return fmt.Errorf("unable to create destination file [%s]: %q", dest, err)
- }
- defer df.Close()
-
- _, err = io.Copy(df, sf)
- if err != nil {
- return fmt.Errorf("unable to copy [%s] to [%s]: %q", source, dest, err)
- }
-
- if err := os.Chmod(dest, fi.Mode()); err != nil {
- return fmt.Errorf("unable to close destination file: %q", err)
- }
- return nil
-}
+++ /dev/null
-/*
-Copyright 2018 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package main
-
-import (
- "fmt"
- "io"
- "os"
- "path/filepath"
- "strings"
-
- "k8s.io/klog/v2"
-)
-
-// DataDirectory provides utilities for initializing and backing up an
-// etcd "data-dir" as well as managing a version.txt file to track the
-// etcd server version and storage version of the etcd data in the
-// directory.
-type DataDirectory struct {
- path string
- versionFile *VersionFile
-}
-
-// OpenOrCreateDataDirectory opens a data directory, creating the directory
-// if it doesn't not already exist.
-func OpenOrCreateDataDirectory(path string) (*DataDirectory, error) {
- exists, err := exists(path)
- if err != nil {
- return nil, err
- }
- if !exists {
- klog.Infof("data directory '%s' does not exist, creating it", path)
- err := os.MkdirAll(path, 0777)
- if err != nil {
- return nil, fmt.Errorf("failed to create data directory %s: %v", path, err)
- }
- }
- versionFile := &VersionFile{
- path: filepath.Join(path, versionFilename),
- }
- return &DataDirectory{path, versionFile}, nil
-}
-
-// Initialize set the version.txt to the target version if the data
-// directory is empty. If the data directory is non-empty, no
-// version.txt file will be written since the actual version of etcd
-// used to create the data is unknown.
-func (d *DataDirectory) Initialize(target *EtcdVersionPair) error {
- isEmpty, err := d.IsEmpty()
- if err != nil {
- return err
- }
- if isEmpty {
- klog.Infof("data directory '%s' is empty, writing target version '%s' to version.txt", d.path, target)
- err = d.versionFile.Write(target)
- if err != nil {
- return fmt.Errorf("failed to write version.txt to '%s': %v", d.path, err)
- }
- return nil
- }
- return nil
-}
-
-// Backup creates a backup copy of data directory.
-func (d *DataDirectory) Backup() error {
- backupDir := fmt.Sprintf("%s.bak", d.path)
- err := os.RemoveAll(backupDir)
- if err != nil {
- return err
- }
- err = os.MkdirAll(backupDir, 0777)
- if err != nil {
- return err
- }
- err = copyDirectory(d.path, backupDir)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-// IsEmpty returns true if the data directory is entirely empty.
-func (d *DataDirectory) IsEmpty() (bool, error) {
- dir, err := os.Open(d.path)
- if err != nil {
- return false, fmt.Errorf("failed to open data directory %s: %v", d.path, err)
- }
- defer dir.Close()
- _, err = dir.Readdirnames(1)
- if err == io.EOF {
- return true, nil
- }
- return false, err
-}
-
-// String returns the data directory path.
-func (d *DataDirectory) String() string {
- return d.path
-}
-
-// VersionFile provides utilities for reading and writing version.txt files
-// to etcd "data-dir" for tracking the etcd server and storage versions
-// of the data in the directory.
-type VersionFile struct {
- path string
-}
-
-func (v *VersionFile) nextPath() string {
- return fmt.Sprintf("%s-next", v.path)
-}
-
-// Exists returns true if a version.txt file exists on the file system.
-func (v *VersionFile) Exists() (bool, error) {
- return exists(v.path)
-}
-
-// Read parses the version.txt file and returns it's contents.
-func (v *VersionFile) Read() (*EtcdVersionPair, error) {
- data, err := os.ReadFile(v.path)
- if err != nil {
- return nil, fmt.Errorf("failed to read version file %s: %v", v.path, err)
- }
- txt := strings.TrimSpace(string(data))
- vp, err := ParseEtcdVersionPair(txt)
- if err != nil {
- return nil, fmt.Errorf("failed to parse etcd '<version>/<storage-version>' string from version.txt file contents '%s': %v", txt, err)
- }
- return vp, nil
-}
-
-// equals returns true iff VersionFile exists and contains given EtcdVersionPair.
-func (v *VersionFile) equals(vp *EtcdVersionPair) (bool, error) {
- exists, err := v.Exists()
- if err != nil {
- return false, err
- }
- if !exists {
- return false, nil
- }
- cvp, err := v.Read()
- if err != nil {
- return false, err
- }
- return vp.Equals(cvp), nil
-}
-
-// Write creates or overwrites the contents of the version.txt file with the given EtcdVersionPair.
-func (v *VersionFile) Write(vp *EtcdVersionPair) error {
- // We do write only if file content differs from given EtcdVersionPair.
- isUpToDate, err := v.equals(vp)
- if err != nil {
- return fmt.Errorf("failed to to check if version file %s should be changed: %v", v.path, err)
- }
- if isUpToDate {
- return nil
- }
- // We do write + rename instead of just write to protect from version.txt
- // corruption under full disk condition.
- // See https://github.com/kubernetes/kubernetes/issues/98989.
- err = os.WriteFile(v.nextPath(), []byte(vp.String()), 0666)
- if err != nil {
- return fmt.Errorf("failed to write new version file %s: %v", v.nextPath(), err)
- }
- return os.Rename(v.nextPath(), v.path)
-}
-
-func exists(path string) (bool, error) {
- if _, err := os.Stat(path); os.IsNotExist(err) {
- return false, nil
- } else if err != nil {
- return false, err
- }
- return true, nil
-}
+++ /dev/null
-/*
-Copyright 2018 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package main
-
-import (
- "os"
- "path/filepath"
- "testing"
-
- "github.com/blang/semver/v4"
-)
-
-var (
- latestVersion = semver.MustParse("3.1.12")
-)
-
-func TestExistingDataDirWithVersionFile(t *testing.T) {
- d, err := OpenOrCreateDataDirectory("testdata/datadir_with_version")
- if err != nil {
- t.Fatalf("Failed to open data dir: %v", err)
- }
- isEmpty, err := d.IsEmpty()
- if err != nil {
- t.Fatalf("Failed to check if data dir is empty: %v", err)
- }
- if isEmpty {
- t.Errorf("Expected non-empty data directory to exist")
- }
- exists, err := d.versionFile.Exists()
- if err != nil {
- t.Fatal(err)
- }
- if !exists {
- t.Fatalf("Expected version file %s to exist", d.versionFile.path)
- }
- vp, err := d.versionFile.Read()
- if err != nil {
- t.Fatalf("Failed to read version file %s: %v", d.versionFile.path, err)
- }
- expectedVersion := &EtcdVersionPair{&EtcdVersion{latestVersion}, storageEtcd3}
- if !vp.Equals(expectedVersion) {
- t.Errorf("Expected version file to contain %s, but got %s", expectedVersion, vp)
- }
-}
-
-func TestExistingDataDirWithoutVersionFile(t *testing.T) {
- targetVersion := &EtcdVersionPair{&EtcdVersion{latestVersion}, storageEtcd3}
-
- d, err := OpenOrCreateDataDirectory("testdata/datadir_without_version")
- if err != nil {
- t.Fatalf("Failed to open data dir: %v", err)
- }
- exists, err := d.versionFile.Exists()
- if err != nil {
- t.Fatal(err)
- }
- if exists {
- t.Errorf("Expected version file %s not to exist", d.versionFile.path)
- }
- err = d.Initialize(targetVersion)
- if err != nil {
- t.Fatalf("Failed initialize data directory %s: %v", d.path, err)
- }
- exists, err = d.versionFile.Exists()
- if err != nil {
- t.Fatal(err)
- }
- if exists {
- t.Fatalf("Expected version file %s not to exist after initializing non-empty data-dir", d.versionFile.path)
- }
-}
-
-func TestNonexistingDataDir(t *testing.T) {
- targetVersion := &EtcdVersionPair{&EtcdVersion{latestVersion}, storageEtcd3}
- path := newTestPath(t)
- defer os.RemoveAll(path)
- d, err := OpenOrCreateDataDirectory(filepath.Join(path, "data-dir"))
- if err != nil {
- t.Fatalf("Failed to open data dir: %v", err)
- }
- isEmpty, err := d.IsEmpty()
- if err != nil {
- t.Fatalf("Failed to check if data dir is empty: %v", err)
- }
- if !isEmpty {
- t.Errorf("Expected empty data directory to exist")
- }
- err = d.Initialize(targetVersion)
- if err != nil {
- t.Fatalf("Failed initialize data directory %s: %v", d.path, err)
- }
- exists, err := d.versionFile.Exists()
- if err != nil {
- t.Fatal(err)
- }
- if !exists {
- t.Fatalf("Expected version file %s to exist", d.versionFile.path)
- }
- isEmpty, err = d.IsEmpty()
- if err != nil {
- t.Fatalf("Failed to check if data dir is empty: %v", err)
- }
- if isEmpty {
- t.Errorf("Expected non-empty data directory to exist after Initialize()")
- }
- vp, err := d.versionFile.Read()
- if err != nil {
- t.Fatalf("Failed to read version file %s: %v", d.versionFile.path, err)
- }
- if !vp.Equals(targetVersion) {
- t.Errorf("Expected version file to contain %s, but got %s", targetVersion, vp)
- }
-}
-
-func TestBackup(t *testing.T) {
- path := newTestPath(t)
- defer os.RemoveAll(path)
- d, err := OpenOrCreateDataDirectory(filepath.Join(path, "data-dir"))
- if err != nil {
- t.Fatalf("Failed to open data dir: %v", err)
- }
- _, err = os.Create(filepath.Join(path, "data-dir", "empty.txt"))
- if err != nil {
- t.Fatal(err)
- }
- err = d.Backup()
- if err != nil {
- t.Fatalf("Failed to backup data directory %s: %v", d.path, err)
- }
- bak, err := OpenOrCreateDataDirectory(filepath.Join(path, "data-dir.bak"))
- if err != nil {
- t.Fatalf("Failed to open backup data dir: %v", err)
- }
- isEmpty, err := bak.IsEmpty()
- if err != nil {
- t.Fatal(err)
- }
- if isEmpty {
- t.Errorf("Expected non-empty backup directory to exist after Backup()")
- }
-}
-
-func newTestPath(t *testing.T) string {
- path, err := os.MkdirTemp("", "etcd-migrate-test-")
- if err != nil {
- t.Fatalf("Failed to create tmp dir for test: %v", err)
- }
- err = os.Chmod(path, 0777)
- if err != nil {
- t.Fatalf("Failed to granting permission to tmp dir for test: %v", err)
- }
- return path
-}
+++ /dev/null
-//go:build integration
-
-/*
-Copyright 2018 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package main
-
-import (
- "bytes"
- cryptorand "crypto/rand"
- "crypto/rsa"
- "crypto/x509"
- "crypto/x509/pkix"
- "encoding/pem"
- "flag"
- "fmt"
- "math/big"
- "net"
- "os"
- "path/filepath"
- "strings"
- "sync"
- "testing"
- "time"
-
- "github.com/blang/semver/v4"
- "k8s.io/klog/v2"
- netutils "k8s.io/utils/net"
-)
-
-var (
- testSupportedVersions = mustParseSupportedVersions([]string{"3.0.17", "3.1.12"})
- testVersionPrevious = &EtcdVersion{semver.MustParse("3.0.17")}
- testVersionLatest = &EtcdVersion{semver.MustParse("3.1.12")}
-)
-
-func init() {
- // Enable klog which is used in dependencies
- klog.InitFlags(nil)
- flag.Set("logtostderr", "true")
- flag.Set("v", "9")
-}
-
-func TestMigrate(t *testing.T) {
- migrations := []struct {
- title string
- memberCount int
- startVersion string
- endVersion string
- protocol string
- clientListenUrls string
- }{
- // upgrades
- {"v3-v3-up", 1, "3.0.17/etcd3", "3.1.12/etcd3", "https", ""},
- {"oldest-newest-up", 1, "3.0.17/etcd3", "3.1.12/etcd3", "https", ""},
- {"v3-v3-up-with-additional-client-url", 1, "3.0.17/etcd3", "3.1.12/etcd3", "https", "http://127.0.0.1:2379,http://10.128.0.1:2379"},
-
- // warning: v2->v3 ha upgrades not currently supported.
- {"ha-v3-v3-up", 3, "3.0.17/etcd3", "3.1.12/etcd3", "https", ""},
-
- // downgrades
- {"v3-v3-down", 1, "3.1.12/etcd3", "3.0.17/etcd3", "https", ""},
-
- // warning: ha downgrades not yet supported.
- }
-
- for _, m := range migrations {
- t.Run(m.title, func(t *testing.T) {
- start := mustParseEtcdVersionPair(m.startVersion)
- end := mustParseEtcdVersionPair(m.endVersion)
-
- testCfgs := clusterConfig(t, m.title, m.memberCount, m.protocol, m.clientListenUrls)
-
- servers := []*EtcdMigrateServer{}
- for _, cfg := range testCfgs {
- client, err := NewEtcdMigrateClient(cfg)
- if err != nil {
- t.Fatalf("Failed to create client: %v", err)
- }
- server := NewEtcdMigrateServer(cfg, client)
- servers = append(servers, server)
- }
-
- // Start the servers.
- parallel(servers, func(server *EtcdMigrateServer) {
- dataDir, err := OpenOrCreateDataDirectory(server.cfg.dataDirectory)
- if err != nil {
- t.Fatalf("Error opening or creating data directory %s: %v", server.cfg.dataDirectory, err)
- }
- migrator := &Migrator{server.cfg, dataDir, server.client}
- err = migrator.MigrateIfNeeded(start)
- if err != nil {
- t.Fatalf("Migration failed: %v", err)
- }
- err = server.Start(start.version)
- if err != nil {
- t.Fatalf("Failed to start server: %v", err)
- }
- })
-
- // Write a value to each server, read it back.
- parallel(servers, func(server *EtcdMigrateServer) {
- key := fmt.Sprintf("/registry/%s", server.cfg.name)
- value := fmt.Sprintf("value-%s", server.cfg.name)
- err := server.client.Put(start.version, key, value)
- if err != nil {
- t.Fatalf("failed to write text value: %v", err)
- }
-
- checkVal, err := server.client.Get(start.version, key)
- if err != nil {
- t.Errorf("Error getting %s for validation: %v", key, err)
- }
- if checkVal != value {
- t.Errorf("Expected %s from %s but got %s", value, key, checkVal)
- }
- })
-
- // Migrate the servers in series.
- serial(servers, func(server *EtcdMigrateServer) {
- err := server.Stop()
- if err != nil {
- t.Fatalf("Stop server failed: %v", err)
- }
- dataDir, err := OpenOrCreateDataDirectory(server.cfg.dataDirectory)
- if err != nil {
- t.Fatalf("Error opening or creating data directory %s: %v", server.cfg.dataDirectory, err)
- }
- migrator := &Migrator{server.cfg, dataDir, server.client}
- err = migrator.MigrateIfNeeded(end)
- if err != nil {
- t.Fatalf("Migration failed: %v", err)
- }
- err = server.Start(end.version)
- if err != nil {
- t.Fatalf("Start server failed: %v", err)
- }
- })
-
- // Check that all test values can be read back from all the servers.
- parallel(servers, func(server *EtcdMigrateServer) {
- for _, s := range servers {
- key := fmt.Sprintf("/registry/%s", s.cfg.name)
- value := fmt.Sprintf("value-%s", s.cfg.name)
- checkVal, err := server.client.Get(end.version, key)
- if err != nil {
- t.Errorf("Error getting %s from etcd 2.x after rollback from 3.x: %v", key, err)
- }
- if checkVal != value {
- t.Errorf("Expected %s from %s but got %s when reading after rollback from %s to %s", value, key, checkVal, start, end)
- }
- }
- })
-
- // Stop the servers.
- parallel(servers, func(server *EtcdMigrateServer) {
- err := server.Stop()
- if err != nil {
- t.Fatalf("Failed to stop server: %v", err)
- }
- })
-
- // Check that version.txt contains the correct end version.
- parallel(servers, func(server *EtcdMigrateServer) {
- dataDir, err := OpenOrCreateDataDirectory(server.cfg.dataDirectory)
- v, err := dataDir.versionFile.Read()
- if err != nil {
- t.Fatalf("Failed to read version.txt file: %v", err)
- }
- if !v.Equals(end) {
- t.Errorf("Expected version.txt to contain %s but got %s", end, v)
- }
- // Integration tests are run in a docker container with umask of 0022.
- checkPermissions(t, server.cfg.dataDirectory, 0755|os.ModeDir)
- checkPermissions(t, dataDir.versionFile.path, 0644)
- })
- })
- }
-}
-
-func parallel(servers []*EtcdMigrateServer, fn func(server *EtcdMigrateServer)) {
- var wg sync.WaitGroup
- wg.Add(len(servers))
- for _, server := range servers {
- go func(s *EtcdMigrateServer) {
- defer wg.Done()
- fn(s)
- }(server)
- }
- wg.Wait()
-}
-
-func serial(servers []*EtcdMigrateServer, fn func(server *EtcdMigrateServer)) {
- for _, server := range servers {
- fn(server)
- }
-}
-
-func checkPermissions(t *testing.T, path string, expected os.FileMode) {
- info, err := os.Stat(path)
- if err != nil {
- t.Fatalf("Failed to stat file %s: %v", path, err)
- }
- if info.Mode() != expected {
- t.Errorf("Expected permissions for file %s of %s, but got %s", path, expected, info.Mode())
- }
-}
-
-func clusterConfig(t *testing.T, name string, memberCount int, protocol string, clientListenUrls string) []*EtcdMigrateCfg {
- peers := []string{}
- for i := 0; i < memberCount; i++ {
- memberName := fmt.Sprintf("%s-%d", name, i)
- peerPort := uint64(2380 + i*10000)
- peer := fmt.Sprintf("%s=%s://127.0.0.1:%d", memberName, protocol, peerPort)
- peers = append(peers, peer)
- }
- initialCluster := strings.Join(peers, ",")
-
- extraArgs := ""
- if protocol == "https" {
- extraArgs = getOrCreateTLSPeerCertArgs(t)
- }
-
- cfgs := []*EtcdMigrateCfg{}
- for i := 0; i < memberCount; i++ {
- memberName := fmt.Sprintf("%s-%d", name, i)
- peerURL := fmt.Sprintf("%s://127.0.0.1:%d", protocol, uint64(2380+i*10000))
- cfg := &EtcdMigrateCfg{
- binPath: "/usr/local/bin",
- name: memberName,
- initialCluster: initialCluster,
- port: uint64(2379 + i*10000),
- peerListenUrls: peerURL,
- peerAdvertiseUrls: peerURL,
- clientListenUrls: clientListenUrls,
- etcdDataPrefix: "/registry",
- ttlKeysDirectory: "/registry/events",
- supportedVersions: testSupportedVersions,
- dataDirectory: fmt.Sprintf("/tmp/etcd-data-dir-%s", memberName),
- etcdServerArgs: extraArgs,
- }
- cfgs = append(cfgs, cfg)
- }
- return cfgs
-}
-
-func getOrCreateTLSPeerCertArgs(t *testing.T) string {
- spec := TestCertSpec{
- host: "localhost",
- ips: []string{"127.0.0.1"},
- }
- certDir := "/tmp/certs"
- certFile := filepath.Join(certDir, "test.crt")
- keyFile := filepath.Join(certDir, "test.key")
- err := getOrCreateTestCertFiles(certFile, keyFile, spec)
- if err != nil {
- t.Fatalf("failed to create server cert: %v", err)
- }
- return fmt.Sprintf("--peer-client-cert-auth --peer-trusted-ca-file=%s --peer-cert-file=%s --peer-key-file=%s", certFile, certFile, keyFile)
-}
-
-type TestCertSpec struct {
- host string
- names, ips []string // in certificate
-}
-
-func getOrCreateTestCertFiles(certFileName, keyFileName string, spec TestCertSpec) (err error) {
- if _, err := os.Stat(certFileName); err == nil {
- if _, err := os.Stat(keyFileName); err == nil {
- return nil
- }
- }
-
- certPem, keyPem, err := generateSelfSignedCertKey(spec.host, parseIPList(spec.ips), spec.names)
- if err != nil {
- return err
- }
-
- os.MkdirAll(filepath.Dir(certFileName), os.FileMode(0777))
- err = os.WriteFile(certFileName, certPem, os.FileMode(0777))
- if err != nil {
- return err
- }
-
- os.MkdirAll(filepath.Dir(keyFileName), os.FileMode(0777))
- err = os.WriteFile(keyFileName, keyPem, os.FileMode(0777))
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func parseIPList(ips []string) []net.IP {
- var netIPs []net.IP
- for _, ip := range ips {
- netIPs = append(netIPs, netutils.ParseIPSloppy(ip))
- }
- return netIPs
-}
-
-// generateSelfSignedCertKey creates a self-signed certificate and key for the given host.
-// Host may be an IP or a DNS name
-// You may also specify additional subject alt names (either ip or dns names) for the certificate
-func generateSelfSignedCertKey(host string, alternateIPs []net.IP, alternateDNS []string) ([]byte, []byte, error) {
- priv, err := rsa.GenerateKey(cryptorand.Reader, 2048)
- if err != nil {
- return nil, nil, err
- }
-
- template := x509.Certificate{
- SerialNumber: big.NewInt(1),
- Subject: pkix.Name{
- CommonName: fmt.Sprintf("%s@%d", host, time.Now().Unix()),
- },
- NotBefore: time.Unix(0, 0),
- NotAfter: time.Now().Add(time.Hour * 24 * 365 * 100),
-
- KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
- ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
- BasicConstraintsValid: true,
- IsCA: true,
- }
-
- if ip := netutils.ParseIPSloppy(host); ip != nil {
- template.IPAddresses = append(template.IPAddresses, ip)
- } else {
- template.DNSNames = append(template.DNSNames, host)
- }
-
- template.IPAddresses = append(template.IPAddresses, alternateIPs...)
- template.DNSNames = append(template.DNSNames, alternateDNS...)
-
- derBytes, err := x509.CreateCertificate(cryptorand.Reader, &template, &template, &priv.PublicKey, priv)
- if err != nil {
- return nil, nil, err
- }
-
- // Generate cert
- certBuffer := bytes.Buffer{}
- if err := pem.Encode(&certBuffer, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
- return nil, nil, err
- }
-
- // Generate key
- keyBuffer := bytes.Buffer{}
- if err := pem.Encode(&keyBuffer, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil {
- return nil, nil, err
- }
-
- return certBuffer.Bytes(), keyBuffer.Bytes(), nil
-}
-
-// mustParseEtcdVersionPair parses a "<version>/<storage-version>" string to an EtcdVersionPair
-// or panics if the parse fails.
-func mustParseEtcdVersionPair(s string) *EtcdVersionPair {
- pair, err := ParseEtcdVersionPair(s)
- if err != nil {
- panic(err)
- }
- return pair
-}
-
-// mustParseSupportedVersions parses a comma separated list of etcd versions or panics if the parse fails.
-func mustParseSupportedVersions(list []string) SupportedVersions {
- versions, err := ParseSupportedVersions(list)
- if err != nil {
- panic(err)
- }
- return versions
-}
+++ /dev/null
-/*
-Copyright 2018 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package main
-
-import (
- "fmt"
- "path/filepath"
-
- "github.com/spf13/cobra"
- "k8s.io/klog/v2"
-)
-
-const (
- versionFilename = "version.txt"
-)
-
-var (
- migrateCmd = &cobra.Command{
- Short: "Upgrade/downgrade etcd data across multiple versions",
- Long: `Upgrade or downgrade etcd data across multiple versions to the target version
-
-Given a 'bin-dir' directory of etcd and etcdctl binaries, an etcd 'data-dir' with a 'version.txt' file and
-a target etcd version, this tool will upgrade or downgrade the etcd data from the version specified in
-'version.txt' to the target version.
-`,
- Run: func(cmd *cobra.Command, args []string) {
- runMigrate()
- },
- }
- opts = migrateOpts{}
-)
-
-func main() {
- registerFlags(migrateCmd.Flags(), &opts)
- err := migrateCmd.Execute()
- if err != nil {
- klog.Errorf("Failed to execute migratecmd: %s", err)
- }
-}
-
-// runMigrate starts the migration.
-func runMigrate() {
- if err := opts.validateAndDefault(); err != nil {
- klog.Fatalf("%v", err)
- }
- copyBinaries()
-
- target := &EtcdVersionPair{
- version: MustParseEtcdVersion(opts.targetVersion),
- storageVersion: MustParseEtcdStorageVersion(opts.targetStorage),
- }
-
- migrate(
- opts.name, opts.port, opts.peerListenUrls, opts.peerAdvertiseUrls, opts.clientListenUrls,
- opts.binDir, opts.dataDir, opts.etcdDataPrefix, opts.ttlKeysDirectory, opts.initialCluster,
- target, opts.supportedVersions, opts.etcdServerArgs)
-}
-
-func copyBinaries() {
- if val, err := lookupEnv("DO_NOT_MOVE_BINARIES"); err != nil || val != "true" {
- etcdVersioned := fmt.Sprintf("etcd-%s", opts.targetVersion)
- etcdctlVersioned := fmt.Sprintf("etcdctl-%s", opts.targetVersion)
- if err := copyFile(filepath.Join(opts.binDir, etcdVersioned), filepath.Join(opts.binDir, "etcd")); err != nil {
- klog.Fatalf("Failed to copy %s: %v", etcdVersioned, err)
- }
- if err := copyFile(filepath.Join(opts.binDir, etcdctlVersioned), filepath.Join(opts.binDir, "etcdctl")); err != nil {
- klog.Fatalf("Failed to copy %s: %v", etcdctlVersioned, err)
- }
- }
-}
-
-// migrate opens or initializes the etcd data directory, configures the migrator, and starts the migration.
-func migrate(name string, port uint64, peerListenUrls string, peerAdvertiseUrls string, clientListenUrls string,
- binPath string, dataDirPath string, etcdDataPrefix string, ttlKeysDirectory string,
- initialCluster string, target *EtcdVersionPair, bundledVersions SupportedVersions, etcdServerArgs string) {
-
- dataDir, err := OpenOrCreateDataDirectory(dataDirPath)
- if err != nil {
- klog.Fatalf("Error opening or creating data directory %s: %v", dataDirPath, err)
- }
-
- cfg := &EtcdMigrateCfg{
- binPath: binPath,
- name: name,
- port: port,
- peerListenUrls: peerListenUrls,
- peerAdvertiseUrls: peerAdvertiseUrls,
- clientListenUrls: clientListenUrls,
- etcdDataPrefix: etcdDataPrefix,
- ttlKeysDirectory: ttlKeysDirectory,
- initialCluster: initialCluster,
- supportedVersions: bundledVersions,
- dataDirectory: dataDirPath,
- etcdServerArgs: etcdServerArgs,
- }
- client, err := NewEtcdMigrateClient(cfg)
- if err != nil {
- klog.Fatalf("Migration failed: %v", err)
- }
- defer client.Close()
-
- migrator := &Migrator{cfg, dataDir, client}
-
- err = migrator.MigrateIfNeeded(target)
- if err != nil {
- klog.Fatalf("Migration failed: %v", err)
- }
-}
+++ /dev/null
-/*
-Copyright 2018 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package main
-
-import (
- "bytes"
- "context"
- "fmt"
- "os"
- "os/exec"
- "path/filepath"
- "strings"
- "time"
-
- clientv3 "go.etcd.io/etcd/client/v3"
- "google.golang.org/grpc"
- "k8s.io/klog/v2"
-)
-
-// CombinedEtcdClient provides an implementation of EtcdMigrateClient using a combination of the etcd v2 client, v3 client
-// and etcdctl commands called via the shell.
-type CombinedEtcdClient struct {
- cfg *EtcdMigrateCfg
-}
-
-// NewEtcdMigrateClient creates a new EtcdMigrateClient from the given EtcdMigrateCfg.
-func NewEtcdMigrateClient(cfg *EtcdMigrateCfg) (EtcdMigrateClient, error) {
- return &CombinedEtcdClient{cfg}, nil
-}
-
-// Close closes the client and releases any resources it holds.
-func (e *CombinedEtcdClient) Close() error {
- return nil
-}
-
-// SetEtcdVersionKeyValue writes the given version to the etcd 'etcd_version' key.
-// If no error is returned, the write was successful, indicating the etcd server is available
-// and able to perform consensus writes.
-func (e *CombinedEtcdClient) SetEtcdVersionKeyValue(version *EtcdVersion) error {
- return e.Put(version, "etcd_version", version.String())
-}
-
-// Put write a single key value pair to etcd.
-func (e *CombinedEtcdClient) Put(version *EtcdVersion, key, value string) error {
- v3client, err := e.clientV3()
- if err != nil {
- return err
- }
- defer v3client.Close()
- _, err = v3client.KV.Put(context.Background(), key, value)
- return err
-}
-
-// Get reads a single value for a given key.
-func (e *CombinedEtcdClient) Get(version *EtcdVersion, key string) (string, error) {
- v3client, err := e.clientV3()
- if err != nil {
- return "", err
- }
- defer v3client.Close()
- resp, err := v3client.KV.Get(context.Background(), key)
- if err != nil {
- return "", err
- }
- kvs := resp.Kvs
- if len(kvs) != 1 {
- return "", fmt.Errorf("expected exactly one value for key %s but got %d", key, len(kvs))
- }
-
- return string(kvs[0].Value), nil
-}
-
-func (e *CombinedEtcdClient) clientV3() (*clientv3.Client, error) {
- return clientv3.New(clientv3.Config{
- Endpoints: []string{e.endpoint()},
- DialTimeout: 20 * time.Second,
- DialOptions: []grpc.DialOption{
- grpc.WithBlock(), // block until the underlying connection is up
- },
- })
-}
-
-// Backup creates a backup of an etcd2 data directory at the given backupDir.
-func (e *CombinedEtcdClient) Backup(version *EtcdVersion, backupDir string) error {
- // We cannot use etcd/client (v2) to make this call. It is implemented in the etcdctl client code.
- if version.Major != 2 {
- return fmt.Errorf("etcd 2.x required but got version '%s'", version)
- }
- return e.runEtcdctlCommand(version,
- "--debug",
- "backup",
- "--data-dir", e.cfg.dataDirectory,
- "--backup-dir", backupDir,
- )
-}
-
-// Snapshot captures a snapshot from a running etcd3 server and saves it to the given snapshotFile.
-// We cannot use etcd/clientv3 to make this call. It is implemented in the etcdctl client code.
-func (e *CombinedEtcdClient) Snapshot(version *EtcdVersion, snapshotFile string) error {
- if version.Major != 3 {
- return fmt.Errorf("etcd 3.x required but got version '%s'", version)
- }
- return e.runEtcdctlCommand(version,
- "--endpoints", e.endpoint(),
- "snapshot", "save", snapshotFile,
- )
-}
-
-// Restore restores a given snapshotFile into the data directory specified this clients config.
-func (e *CombinedEtcdClient) Restore(version *EtcdVersion, snapshotFile string) error {
- // We cannot use etcd/clientv3 to make this call. It is implemented in the etcdctl client code.
- if version.Major != 3 {
- return fmt.Errorf("etcd 3.x required but got version '%s'", version)
- }
- return e.runEtcdctlCommand(version,
- "snapshot", "restore", snapshotFile,
- "--data-dir", e.cfg.dataDirectory,
- "--name", e.cfg.name,
- "--initial-advertise-peer-urls", e.cfg.peerAdvertiseUrls,
- "--initial-cluster", e.cfg.initialCluster,
- )
-}
-
-// Migrate upgrades a 'etcd2' storage version data directory to a 'etcd3' storage version
-// data directory.
-func (e *CombinedEtcdClient) Migrate(version *EtcdVersion) error {
- // We cannot use etcd/clientv3 to make this call as it is implemented in etcd/etcdctl.
- if version.Major != 3 {
- return fmt.Errorf("etcd 3.x required but got version '%s'", version)
- }
- return e.runEtcdctlCommand(version,
- "migrate",
- "--data-dir", e.cfg.dataDirectory,
- )
-}
-
-func (e *CombinedEtcdClient) runEtcdctlCommand(version *EtcdVersion, args ...string) error {
- etcdctlCmd := exec.Command(filepath.Join(e.cfg.binPath, fmt.Sprintf("etcdctl-%s", version)), args...)
- etcdctlCmd.Env = []string{fmt.Sprintf("ETCDCTL_API=%d", version.Major)}
- etcdctlCmd.Stdout = os.Stdout
- etcdctlCmd.Stderr = os.Stderr
- return etcdctlCmd.Run()
-}
-
-// AttachLease attaches leases of the given leaseDuration to all the etcd objects under
-// ttlKeysDirectory specified in this client's config.
-func (e *CombinedEtcdClient) AttachLease(leaseDuration time.Duration) error {
- ttlKeysPrefix := e.cfg.ttlKeysDirectory
- // Make sure that ttlKeysPrefix is ended with "/" so that we only get children "directories".
- if !strings.HasSuffix(ttlKeysPrefix, "/") {
- ttlKeysPrefix += "/"
- }
- ctx := context.Background()
-
- v3client, err := e.clientV3()
- if err != nil {
- return err
- }
- defer v3client.Close()
- objectsResp, err := v3client.KV.Get(ctx, ttlKeysPrefix, clientv3.WithPrefix())
- if err != nil {
- return fmt.Errorf("error while getting objects to attach to the lease")
- }
-
- lease, err := v3client.Lease.Grant(ctx, int64(leaseDuration/time.Second))
- if err != nil {
- return fmt.Errorf("error while creating lease: %v", err)
- }
- klog.Infof("Lease with TTL: %v created", lease.TTL)
-
- klog.Infof("Attaching lease to %d entries", len(objectsResp.Kvs))
- for _, kv := range objectsResp.Kvs {
- putResp, err := v3client.KV.Put(ctx, string(kv.Key), string(kv.Value), clientv3.WithLease(lease.ID), clientv3.WithPrevKV())
- if err != nil {
- klog.Errorf("Error while attaching lease to: %s", string(kv.Key))
- }
- if !bytes.Equal(putResp.PrevKv.Value, kv.Value) {
- return fmt.Errorf("concurrent access to key detected when setting lease on %s, expected previous value of %s but got %s",
- kv.Key, kv.Value, putResp.PrevKv.Value)
- }
- }
- return nil
-}
-
-func (e *CombinedEtcdClient) endpoint() string {
- return fmt.Sprintf("http://127.0.0.1:%d", e.cfg.port)
-}
+++ /dev/null
-/*
-Copyright 2018 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package main
-
-import (
- "fmt"
- "os"
- "os/exec"
- "strings"
- "time"
-
- "k8s.io/klog/v2"
-)
-
-// EtcdMigrateServer manages starting and stopping a versioned etcd server binary.
-type EtcdMigrateServer struct {
- cfg *EtcdMigrateCfg
- client EtcdMigrateClient
- cmd *exec.Cmd
-}
-
-// NewEtcdMigrateServer creates a EtcdMigrateServer for starting and stopping a etcd server at the given version.
-func NewEtcdMigrateServer(cfg *EtcdMigrateCfg, client EtcdMigrateClient) *EtcdMigrateServer {
- return &EtcdMigrateServer{cfg: cfg, client: client}
-}
-
-// Start starts an etcd server as a separate process, waits until it has started, and returns a exec.Cmd.
-// TODO: Add support for listening to client via TLS.
-func (r *EtcdMigrateServer) Start(version *EtcdVersion) error {
- etcdCmd := exec.Command(
- fmt.Sprintf("%s/etcd-%s", r.cfg.binPath, version),
- "--name", r.cfg.name,
- "--initial-cluster", r.cfg.initialCluster,
- "--debug",
- "--data-dir", r.cfg.dataDirectory,
- "--listen-client-urls", r.cfg.clientListenUrls,
- "--advertise-client-urls", fmt.Sprintf("http://127.0.0.1:%d", r.cfg.port),
- "--listen-peer-urls", r.cfg.peerListenUrls,
- "--initial-advertise-peer-urls", r.cfg.peerAdvertiseUrls,
- )
- if r.cfg.etcdServerArgs != "" {
- extraArgs := strings.Fields(r.cfg.etcdServerArgs)
- etcdCmd.Args = append(etcdCmd.Args, extraArgs...)
- }
- fmt.Printf("Starting server %s: %+v\n", r.cfg.name, etcdCmd.Args)
-
- etcdCmd.Stdout = os.Stdout
- etcdCmd.Stderr = os.Stderr
- err := etcdCmd.Start()
- if err != nil {
- return err
- }
- interval := time.NewTicker(time.Millisecond * 500)
- defer interval.Stop()
- done := make(chan bool)
- go func() {
- time.Sleep(time.Minute * 2)
- close(done)
- }()
- for {
- select {
- case <-interval.C:
- err := r.client.SetEtcdVersionKeyValue(version)
- if err != nil {
- klog.Infof("Still waiting for etcd to start, current error: %v", err)
- // keep waiting
- } else {
- klog.Infof("Etcd on port %d is up.", r.cfg.port)
- r.cmd = etcdCmd
- return nil
- }
- case <-done:
- err = etcdCmd.Process.Kill()
- if err != nil {
- return fmt.Errorf("error killing etcd: %v", err)
- }
- return fmt.Errorf("timed out waiting for etcd on port %d", r.cfg.port)
- }
- }
-}
-
-// Stop terminates the etcd server process. If the etcd server process has not been started
-// or is not still running, this returns an error.
-func (r *EtcdMigrateServer) Stop() error {
- if r.cmd == nil {
- return fmt.Errorf("cannot stop EtcdMigrateServer that has not been started")
- }
- err := r.cmd.Process.Signal(os.Interrupt)
- if err != nil {
- return fmt.Errorf("error sending SIGINT to etcd for graceful shutdown: %v", err)
- }
- gracefulWait := time.Minute * 2
- stopped := make(chan bool)
- timedout := make(chan bool)
- go func() {
- time.Sleep(gracefulWait)
- close(timedout)
- }()
- go func() {
- select {
- case <-stopped:
- return
- case <-timedout:
- klog.Infof("etcd server has not terminated gracefully after %s, killing it.", gracefulWait)
- r.cmd.Process.Kill()
- return
- }
- }()
- err = r.cmd.Wait()
- close(stopped)
- if exiterr, ok := err.(*exec.ExitError); ok {
- klog.Infof("etcd server stopped (signal: %s)", exiterr.Error())
- // stopped
- } else if err != nil {
- return fmt.Errorf("error waiting for etcd to stop: %v", err)
- }
- klog.Infof("Stopped etcd server %s", r.cfg.name)
- return nil
-}
+++ /dev/null
-/*
-Copyright 2018 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package main
-
-import (
- "fmt"
- "os"
- "time"
-
- "github.com/blang/semver/v4"
- "k8s.io/klog/v2"
-)
-
-// EtcdMigrateCfg provides all configuration required to perform etcd data upgrade/downgrade migrations.
-type EtcdMigrateCfg struct {
- binPath string
- name string
- initialCluster string
- port uint64
- peerListenUrls string
- peerAdvertiseUrls string
- clientListenUrls string
- etcdDataPrefix string
- ttlKeysDirectory string
- supportedVersions SupportedVersions
- dataDirectory string
- etcdServerArgs string
-}
-
-// EtcdMigrateClient defines the etcd client operations required to perform migrations.
-type EtcdMigrateClient interface {
- SetEtcdVersionKeyValue(version *EtcdVersion) error
- Get(version *EtcdVersion, key string) (string, error)
- Put(version *EtcdVersion, key, value string) error
- Backup(version *EtcdVersion, backupDir string) error
- Snapshot(version *EtcdVersion, snapshotFile string) error
- Restore(version *EtcdVersion, snapshotFile string) error
- Migrate(version *EtcdVersion) error
- AttachLease(leaseDuration time.Duration) error
- Close() error
-}
-
-// Migrator manages etcd data migrations.
-type Migrator struct {
- cfg *EtcdMigrateCfg // TODO: don't wire this directly in
- dataDirectory *DataDirectory
- client EtcdMigrateClient
-}
-
-// MigrateIfNeeded upgrades or downgrades the etcd data directory to the given target version.
-func (m *Migrator) MigrateIfNeeded(target *EtcdVersionPair) error {
- klog.Infof("Starting migration to %s", target)
- err := m.dataDirectory.Initialize(target)
- if err != nil {
- return fmt.Errorf("failed to initialize data directory %s: %v", m.dataDirectory.path, err)
- }
-
- var current *EtcdVersionPair
- vfExists, err := m.dataDirectory.versionFile.Exists()
- if err != nil {
- return err
- }
- if vfExists {
- current, err = m.dataDirectory.versionFile.Read()
- if err != nil {
- return err
- }
- } else {
- return fmt.Errorf("existing data directory '%s' is missing version.txt file, unable to migrate", m.dataDirectory.path)
- }
-
- for {
- klog.Infof("Converging current version '%s' to target version '%s'", current, target)
- currentNextMinorVersion := &EtcdVersion{Version: semver.Version{Major: current.version.Major, Minor: current.version.Minor + 1}}
- switch {
- case current.version.MajorMinorEquals(target.version) || currentNextMinorVersion.MajorMinorEquals(target.version):
- klog.Infof("current version '%s' equals or is one minor version previous of target version '%s' - migration complete", current, target)
- err = m.dataDirectory.versionFile.Write(target)
- if err != nil {
- return fmt.Errorf("failed to write version.txt to '%s': %v", m.dataDirectory.path, err)
- }
- return nil
- case current.storageVersion == storageEtcd2 && target.storageVersion == storageEtcd3:
- return fmt.Errorf("upgrading from etcd2 storage to etcd3 storage is not supported")
- case current.version.Major == 3 && target.version.Major == 2:
- return fmt.Errorf("downgrading from etcd 3.x to 2.x is not supported")
- case current.version.Major == target.version.Major && current.version.Minor < target.version.Minor:
- stepVersion := m.cfg.supportedVersions.NextVersionPair(current)
- klog.Infof("upgrading etcd from %s to %s", current, stepVersion)
- current, err = m.minorVersionUpgrade(current, stepVersion)
- case current.version.Major == 3 && target.version.Major == 3 && current.version.Minor > target.version.Minor:
- klog.Infof("rolling etcd back from %s to %s", current, target)
- current, err = m.rollbackEtcd3MinorVersion(current, target)
- }
- if err != nil {
- return err
- }
- }
-}
-
-func (m *Migrator) rollbackEtcd3MinorVersion(current *EtcdVersionPair, target *EtcdVersionPair) (*EtcdVersionPair, error) {
- if target.version.Minor != current.version.Minor-1 {
- return nil, fmt.Errorf("rollback from %s to %s not supported, only rollbacks to the previous minor version are supported", current.version, target.version)
- }
-
- klog.Infof("Performing etcd %s -> %s rollback", current.version, target.version)
- err := m.dataDirectory.Backup()
- if err != nil {
- return nil, err
- }
-
- snapshotFilename := fmt.Sprintf("%s.snapshot.db", m.dataDirectory.path)
- err = os.Remove(snapshotFilename)
- if err != nil && !os.IsNotExist(err) {
- return nil, fmt.Errorf("failed to clean snapshot file before rollback: %v", err)
- }
-
- // Start current version of etcd.
- runner := m.newServer()
- klog.Infof("Starting etcd version %s to capture rollback snapshot.", current.version)
- err = runner.Start(current.version)
- if err != nil {
- klog.Fatalf("Unable to automatically downgrade etcd: starting etcd version %s to capture rollback snapshot failed: %v", current.version, err)
- return nil, err
- }
-
- klog.Infof("Snapshotting etcd %s to %s", current.version, snapshotFilename)
- err = m.client.Snapshot(current.version, snapshotFilename)
- if err != nil {
- return nil, err
- }
-
- err = runner.Stop()
- if err != nil {
- return nil, err
- }
-
- klog.Info("Backing up data before rolling back")
- backupDir := fmt.Sprintf("%s.bak", m.dataDirectory)
- err = os.RemoveAll(backupDir)
- if err != nil {
- return nil, err
- }
- origInfo, err := os.Stat(m.dataDirectory.path)
- if err != nil {
- return nil, err
- }
- err = os.Rename(m.dataDirectory.path, backupDir)
- if err != nil {
- return nil, err
- }
-
- klog.Infof("Restoring etcd %s from %s", target.version, snapshotFilename)
- err = m.client.Restore(target.version, snapshotFilename)
- if err != nil {
- return nil, err
- }
- err = os.Chmod(m.dataDirectory.path, origInfo.Mode())
- if err != nil {
- return nil, err
- }
-
- return target, nil
-}
-
-func (m *Migrator) minorVersionUpgrade(current *EtcdVersionPair, target *EtcdVersionPair) (*EtcdVersionPair, error) {
- runner := m.newServer()
-
- // Do the migration step, by just starting etcd in the target version.
- err := runner.Start(target.version)
- if err != nil {
- return nil, err
- }
- err = runner.Stop()
- return target, err
-}
-
-func (m *Migrator) newServer() *EtcdMigrateServer {
- return NewEtcdMigrateServer(m.cfg, m.client)
-}
+++ /dev/null
-/*
-Copyright 2018 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package main
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "regexp"
- "strings"
-
- flag "github.com/spf13/pflag"
- "k8s.io/klog/v2"
-)
-
-var (
- supportedEtcdVersions = []string{"3.4.18", "3.5.21", "3.6.4"}
-)
-
-const (
- etcdNameEnv = "ETCD_NAME"
- etcdHostnameEnv = "ETCD_HOSTNAME"
- hostnameEnv = "HOSTNAME"
- dataDirEnv = "DATA_DIRECTORY"
- initialClusterEnv = "INITIAL_CLUSTER"
- initialClusterFmt = "%s=http://localhost:%d"
- peerListenUrlsEnv = "LISTEN_PEER_URLS"
- peerListenUrlsFmt = "http://localhost:%d"
- peerAdvertiseUrlsEnv = "INITIAL_ADVERTISE_PEER_URLS"
- peerAdvertiseUrlsFmt = "http://localhost:%d"
- clientListenURLsEnv = "LISTEN_CLIENT_URLS"
- clientListenURLFmt = "http://127.0.0.1:%d"
- targetVersionEnv = "TARGET_VERSION"
- targetStorageEnv = "TARGET_STORAGE"
- etcdDataPrefixEnv = "ETCD_DATA_PREFIX"
- etcdDataPrefixDefault = "/registry"
- ttlKeysDirectoryFmt = "%s/events"
- etcdServerArgsEnv = "ETCD_CREDS"
-)
-
-type migrateOpts struct {
- name string
- port uint64
- peerPort uint64
- peerListenUrls string
- peerAdvertiseUrls string
- binDir string
- dataDir string
- bundledVersions []string
- supportedVersions SupportedVersions
- etcdDataPrefix string
- ttlKeysDirectory string
- initialCluster string
- targetVersion string
- targetStorage string
- etcdServerArgs string
- clientListenUrls string
-}
-
-func registerFlags(flags *flag.FlagSet, opt *migrateOpts) {
- flags.StringVar(&opts.name, "name", "",
- "etcd cluster member name. If unset fallbacks to defaults to ETCD_NAME env, if unset defaults to etcd-<ETCD_HOSTNAME> env, if unset defaults to etcd-<HOSTNAME> env.")
- flags.Uint64Var(&opts.port, "port", 0,
- "etcd client port to use during migration operations. "+
- "This should be a different port than typically used by etcd to avoid clients accidentally connecting during upgrade/downgrade operations. "+
- "If unset default to 18629 or 18631 depending on <data-dir>.")
- flags.Uint64Var(&opts.peerPort, "peer-port", 0,
- "etcd peer port to use during migration operations. If unset defaults to 2380 or 2381 depending on <data-dir>.")
- flags.StringVar(&opts.peerListenUrls, "listen-peer-urls", "",
- "etcd --listen-peer-urls flag. If unset, fallbacks to LISTEN_PEER_URLS env and if unset defaults to http://localhost:<peer-port>.")
- flags.StringVar(&opts.peerAdvertiseUrls, "initial-advertise-peer-urls", "",
- "etcd --initial-advertise-peer-urls flag. If unset fallbacks to INITIAL_ADVERTISE_PEER_URLS env and if unset defaults to http://localhost:<peer-port>.")
- flags.StringVar(&opts.clientListenUrls, "listen-client-urls", "",
- "etcd --listen-client-urls flag. If unset, fallbacks to LISTEN_CLIENT_URLS env, and if unset defaults to http://127.0.0.1:<port>.")
- flags.StringVar(&opts.binDir, "bin-dir", "/usr/local/bin",
- "directory of etcd and etcdctl binaries, must contain etcd-<version> and etcdctl-<version> for each version listed in <bundled-versions>.")
- flags.StringVar(&opts.dataDir, "data-dir", "",
- "etcd data directory of etcd server to migrate. If unset fallbacks to DATA_DIRECTORY env.")
- flags.StringSliceVar(&opts.bundledVersions, "bundled-versions", supportedEtcdVersions,
- "comma separated list of etcd binary versions present under the bin-dir.")
- flags.StringVar(&opts.etcdDataPrefix, "etcd-data-prefix", "",
- "etcd key prefix under which all objects are kept. If unset fallbacks to ETCD_DATA_PREFIX env and if unset defaults to /registry.")
- flags.StringVar(&opts.ttlKeysDirectory, "ttl-keys-directory", "",
- "etcd key prefix under which all keys with TTLs are kept. Defaults to <etcd-data-prefix>/events")
- flags.StringVar(&opts.initialCluster, "initial-cluster", "",
- "comma separated list of name=endpoint pairs. If unset fallbacks to INITIAL_CLUSTER and if unset defaults to <etcd-name>=https://localhost:<peer-port>.")
- flags.StringVar(&opts.targetVersion, "target-version", "",
- "version of etcd to migrate to. Format must be <major>.<minor>.<patch>. If unset fallbacks to TARGET_VERSION env.")
- flags.StringVar(&opts.targetStorage, "target-storage", "",
- "storage version of etcd to migrate to, one of: etcd2, etcd3. If unset fallbacks to TARGET_STORAGE env.")
- flags.StringVar(&opts.etcdServerArgs, "etcd-server-extra-args", "",
- "additional etcd server args for starting etcd servers during migration steps, need to set TLS certs flags for multi-member clusters using mTLS for communication. "+
- "If unset fallbacks to ETCD_CREDS env.")
-}
-
-func lookupEnv(env string) (string, error) {
- result, ok := os.LookupEnv(env)
- if !ok || len(result) == 0 {
- return result, fmt.Errorf("%s variable unset - expected failure", env)
- }
- return result, nil
-}
-
-func fallbackToEnv(flag, env string) (string, error) {
- klog.Infof("--%s unset - falling back to %s variable", flag, env)
- return lookupEnv(env)
-}
-
-func fallbackToEnvWithDefault(flag, env, def string) string {
- if value, err := lookupEnv(env); err == nil {
- return value
- }
- klog.Warningf("%s variable for %s flag unset - defaulting to %s", env, flag, def)
- return def
-}
-
-func defaultName() (string, error) {
- if etcdName, err := lookupEnv(etcdNameEnv); err == nil {
- return etcdName, nil
- }
- klog.Warningf("%s variable unset - falling back to etcd-%s variable", etcdNameEnv, etcdHostnameEnv)
- if etcdHostname, err := lookupEnv(etcdHostnameEnv); err == nil {
- return fmt.Sprintf("etcd-%s", etcdHostname), nil
- }
- klog.Warningf("%s variable unset - falling back to etcd-%s variable", etcdHostnameEnv, hostnameEnv)
- if hostname, err := lookupEnv(hostnameEnv); err == nil {
- return fmt.Sprintf("etcd-%s", hostname), nil
- }
- return "", fmt.Errorf("defaulting --name failed due to all ETCD_NAME, ETCD_HOSTNAME and HOSTNAME unset")
-}
-
-func (opts *migrateOpts) validateAndDefault() error {
- var err error
-
- if opts.name == "" {
- klog.Infof("--name unset - falling back to %s variable", etcdNameEnv)
- if opts.name, err = defaultName(); err != nil {
- return err
- }
- }
-
- if opts.dataDir == "" {
- if opts.dataDir, err = fallbackToEnv("data-dir", dataDirEnv); err != nil {
- return err
- }
- }
-
- etcdEventsRE := regexp.MustCompile("event")
- if opts.port == 0 {
- if etcdEventsRE.MatchString(opts.dataDir) {
- opts.port = 18631
- } else {
- opts.port = 18629
- }
- klog.Infof("--port unset - defaulting to %d", opts.port)
- }
- if opts.peerPort == 0 {
- if etcdEventsRE.MatchString(opts.dataDir) {
- opts.peerPort = 2381
- } else {
- opts.peerPort = 2380
- }
- klog.Infof("--peer-port unset - defaulting to %d", opts.peerPort)
- }
-
- if opts.initialCluster == "" {
- def := fmt.Sprintf(initialClusterFmt, opts.name, opts.peerPort)
- opts.initialCluster = fallbackToEnvWithDefault("initial-cluster", initialClusterEnv, def)
- }
-
- if opts.peerListenUrls == "" {
- def := fmt.Sprintf(peerListenUrlsFmt, opts.peerPort)
- opts.peerListenUrls = fallbackToEnvWithDefault("listen-peer-urls", peerListenUrlsEnv, def)
- }
-
- if opts.peerAdvertiseUrls == "" {
- def := fmt.Sprintf(peerAdvertiseUrlsFmt, opts.peerPort)
- opts.peerAdvertiseUrls = fallbackToEnvWithDefault("initial-advertise-peer-urls", peerAdvertiseUrlsEnv, def)
- }
-
- if opts.clientListenUrls == "" {
- def := fmt.Sprintf(clientListenURLFmt, opts.port)
- opts.clientListenUrls = fallbackToEnvWithDefault("listen-client-urls", clientListenURLsEnv, def)
- }
-
- if opts.targetVersion == "" {
- if opts.targetVersion, err = fallbackToEnv("target-version", targetVersionEnv); err != nil {
- return err
- }
- }
-
- if opts.targetStorage == "" {
- if opts.targetStorage, err = fallbackToEnv("target-storage", targetStorageEnv); err != nil {
- return err
- }
- }
-
- if opts.etcdDataPrefix == "" {
- opts.etcdDataPrefix = fallbackToEnvWithDefault("etcd-data-prefix", etcdDataPrefixEnv, etcdDataPrefixDefault)
- }
-
- if opts.ttlKeysDirectory == "" {
- opts.ttlKeysDirectory = fmt.Sprintf(ttlKeysDirectoryFmt, opts.etcdDataPrefix)
- klog.Infof("--ttl-keys-directory unset - defaulting to %s", opts.ttlKeysDirectory)
- }
-
- if opts.etcdServerArgs == "" {
- opts.etcdServerArgs = fallbackToEnvWithDefault("etcd-server-extra-args", etcdServerArgsEnv, "")
- }
-
- if opts.supportedVersions, err = ParseSupportedVersions(opts.bundledVersions); err != nil {
- return fmt.Errorf("failed to parse --bundled-versions: %v", err)
- }
-
- if err := validateBundledVersions(opts.supportedVersions, opts.binDir); err != nil {
- return fmt.Errorf("failed to validate that 'etcd-<version>' and 'etcdctl-<version>' binaries exist in --bin-dir '%s' for all --bundled-versions '%s': %v",
- opts.binDir, strings.Join(opts.bundledVersions, ","), err)
- }
- return nil
-}
-
-// validateBundledVersions checks that 'etcd-<version>' and 'etcdctl-<version>' binaries exist in the binDir
-// for each version in the bundledVersions list.
-func validateBundledVersions(bundledVersions SupportedVersions, binDir string) error {
- for _, v := range bundledVersions {
- for _, binaryName := range []string{"etcd", "etcdctl"} {
- fn := filepath.Join(binDir, fmt.Sprintf("%s-%s", binaryName, v))
- if _, err := os.Stat(fn); err != nil {
- return fmt.Errorf("failed to validate '%s' binary exists for bundled-version '%s': %v", fn, v, err)
- }
-
- }
- }
- return nil
-}
+++ /dev/null
-/*
-Copyright 2018 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package main
-
-import (
- "os"
- "testing"
-)
-
-func setEnvVar(t *testing.T, env, val string, exists bool) {
- if exists {
- t.Setenv(env, val)
- } else if prev, ok := os.LookupEnv(env); ok {
- t.Cleanup(func() { os.Setenv(env, prev) })
-
- if err := os.Unsetenv(env); err != nil {
- t.Errorf("couldn't unset env %s: %v", env, err)
- }
- }
-}
-
-func TestFallbackToEnv(t *testing.T) {
- testCases := []struct {
- desc string
- env string
- value string
- valueSet bool
- expectedValue string
- expectedError bool
- }{
- {
- desc: "value unset",
- env: "FOO",
- valueSet: false,
- expectedValue: "",
- expectedError: true,
- },
- {
- desc: "value set empty",
- env: "FOO",
- value: "",
- valueSet: true,
- expectedValue: "",
- expectedError: true,
- },
- {
- desc: "value set",
- env: "FOO",
- value: "foo",
- valueSet: true,
- expectedValue: "foo",
- expectedError: false,
- },
- }
-
- for _, test := range testCases {
- t.Run(test.desc, func(t *testing.T) {
- setEnvVar(t, test.env, test.value, test.valueSet)
- value, err := fallbackToEnv("some-flag", test.env)
- if test.expectedError {
- if err == nil {
- t.Errorf("expected error, got: %v", err)
- }
- } else {
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- }
- if value != test.expectedValue {
- t.Errorf("unexpected result: %s, expected: %s", value, test.expectedValue)
- }
- }
- })
- }
-}
-
-func TestFallbackToEnvWithDefault(t *testing.T) {
- testCases := []struct {
- desc string
- env string
- value string
- valueSet bool
- defaultValue string
- expectedValue string
- expectedError bool
- }{
- {
- desc: "value unset",
- env: "FOO",
- valueSet: false,
- defaultValue: "default",
- expectedValue: "default",
- },
- {
- desc: "value set empty",
- env: "FOO",
- value: "",
- valueSet: true,
- defaultValue: "default",
- expectedValue: "default",
- },
- {
- desc: "value set",
- env: "FOO",
- value: "foo",
- valueSet: true,
- defaultValue: "default",
- expectedValue: "foo",
- },
- }
-
- for _, test := range testCases {
- t.Run(test.desc, func(t *testing.T) {
- setEnvVar(t, test.env, test.value, test.valueSet)
- value := fallbackToEnvWithDefault("some-flag", test.env, test.defaultValue)
- if value != test.expectedValue {
- t.Errorf("unexpected result: %s, expected: %s", value, test.expectedValue)
- }
- })
- }
-}
+++ /dev/null
-3.1.12/etcd3
+++ /dev/null
-//go:build !windows
-
-/*
-Copyright 2020 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package main
-
-import (
- "github.com/mrunalp/fileutils"
-)
-
-func copyDirectory(source string, dest string) error {
- return fileutils.CopyDirectory(source, dest)
-}
+++ /dev/null
-//go:build windows
-
-/*
-Copyright 2020 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package main
-
-import "fmt"
-
-func copyDirectory(source string, dest string) error {
- return fmt.Errorf("no support for windows")
-}
+++ /dev/null
-/*
-Copyright 2018 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package main
-
-import (
- "fmt"
- "strings"
-
- "github.com/blang/semver/v4"
-)
-
-// EtcdVersion specifies an etcd server binaries SemVer.
-type EtcdVersion struct {
- semver.Version
-}
-
-// ParseEtcdVersion parses a SemVer string to an EtcdVersion.
-func ParseEtcdVersion(s string) (*EtcdVersion, error) {
- v, err := semver.Make(s)
- if err != nil {
- return nil, err
- }
- return &EtcdVersion{v}, nil
-}
-
-// MustParseEtcdVersion parses a SemVer string to an EtcdVersion and panics if the parse fails.
-func MustParseEtcdVersion(s string) *EtcdVersion {
- return &EtcdVersion{semver.MustParse(s)}
-}
-
-// String returns the version in SemVer string format.
-func (v *EtcdVersion) String() string {
- return v.Version.String()
-}
-
-// Equals returns true if the versions are exactly equal.
-func (v *EtcdVersion) Equals(o *EtcdVersion) bool {
- return v.Version.Equals(o.Version)
-}
-
-// MajorMinorEquals returns true if the major and minor parts of the versions are equal;
-// if only patch versions differ, this returns true.
-func (v *EtcdVersion) MajorMinorEquals(o *EtcdVersion) bool {
- return v.Major == o.Major && v.Minor == o.Minor
-}
-
-// EtcdStorageVersion identifies the storage version of an etcd data directory.
-type EtcdStorageVersion int
-
-const (
- storageUnknown EtcdStorageVersion = iota
- storageEtcd2
- storageEtcd3
-)
-
-// ParseEtcdStorageVersion parses an etcd storage version string to an EtcdStorageVersion.
-func ParseEtcdStorageVersion(s string) (EtcdStorageVersion, error) {
- switch s {
- case "etcd2":
- return storageEtcd2, nil
- case "etcd3":
- return storageEtcd3, nil
- default:
- return storageUnknown, fmt.Errorf("unrecognized storage version: %s", s)
- }
-}
-
-// MustParseEtcdStorageVersion parses an etcd storage version string to an EtcdStorageVersion and
-// panics if the parse fails.
-func MustParseEtcdStorageVersion(s string) EtcdStorageVersion {
- version, err := ParseEtcdStorageVersion(s)
- if err != nil {
- panic(err)
- }
- return version
-}
-
-// String returns the text representation of the EtcdStorageVersion, 'etcd2' or 'etcd3'.
-func (v EtcdStorageVersion) String() string {
- switch v {
- case storageEtcd2:
- return "etcd2"
- case storageEtcd3:
- return "etcd3"
- default:
- panic(fmt.Sprintf("enum value %d missing from EtcdStorageVersion String() function", v))
- }
-}
-
-// EtcdVersionPair is composed of an etcd version and storage version.
-type EtcdVersionPair struct {
- version *EtcdVersion
- storageVersion EtcdStorageVersion
-}
-
-// ParseEtcdVersionPair parses a "<version>/<storage-version>" string to an EtcdVersionPair.
-func ParseEtcdVersionPair(s string) (*EtcdVersionPair, error) {
- parts := strings.Split(s, "/")
- if len(parts) != 2 {
- return nil, fmt.Errorf("malformed version file, expected <major>.<minor>.<patch>/<storage> but got %s", s)
- }
- version, err := ParseEtcdVersion(parts[0])
- if err != nil {
- return nil, err
- }
- storageVersion, err := ParseEtcdStorageVersion(parts[1])
- if err != nil {
- return nil, err
- }
- return &EtcdVersionPair{version, storageVersion}, nil
-}
-
-// String returns "<version>/<storage-version>" string of the EtcdVersionPair.
-func (vp *EtcdVersionPair) String() string {
- return fmt.Sprintf("%s/%s", vp.version, vp.storageVersion)
-}
-
-// Equals returns true if both the versions and storage versions are exactly equal.
-func (vp *EtcdVersionPair) Equals(o *EtcdVersionPair) bool {
- return vp.version.Equals(o.version) && vp.storageVersion == o.storageVersion
-}
-
-// SupportedVersions provides a list of etcd versions that are "supported" for some purpose.
-// The list must be sorted from lowest semantic version to high.
-type SupportedVersions []*EtcdVersion
-
-// NextVersion returns the next supported version after the given current version, or nil if no
-// next version exists.
-func (sv SupportedVersions) NextVersion(current *EtcdVersion) *EtcdVersion {
- var nextVersion *EtcdVersion
- for i, supportedVersion := range sv {
- if current.MajorMinorEquals(supportedVersion) && len(sv) > i+1 {
- nextVersion = sv[i+1]
- }
- }
- return nextVersion
-}
-
-// NextVersionPair returns the next supported version after the given current version and infers
-// the storage version from the major version part of the next version.
-func (sv SupportedVersions) NextVersionPair(current *EtcdVersionPair) *EtcdVersionPair {
- nextVersion := sv.NextVersion(current.version)
- if nextVersion == nil {
- return nil
- }
- storageVersion := storageEtcd3
- if nextVersion.Major == 2 {
- storageVersion = storageEtcd2
- }
- return &EtcdVersionPair{version: nextVersion, storageVersion: storageVersion}
-}
-
-// ParseSupportedVersions parses a list of etcd versions.
-func ParseSupportedVersions(list []string) (SupportedVersions, error) {
- var err error
- versions := make(SupportedVersions, len(list))
- for i, v := range list {
- versions[i], err = ParseEtcdVersion(strings.TrimSpace(v))
- if err != nil {
- return nil, err
- }
- }
- return versions, nil
-}
+++ /dev/null
-/*
-Copyright 2018 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package main
-
-import (
- "testing"
-
- "github.com/blang/semver/v4"
-)
-
-func TestSerializeEtcdVersionPair(t *testing.T) {
- cases := []struct {
- versionTxt string
- version *EtcdVersionPair
- match bool
- }{
- {"3.1.2/etcd3", &EtcdVersionPair{&EtcdVersion{semver.MustParse("3.1.2")}, storageEtcd3}, true},
- {"1.1.1-rc.0/etcd3", &EtcdVersionPair{&EtcdVersion{semver.MustParse("1.1.1-rc.0")}, storageEtcd3}, true},
- {"10.100.1000/etcd3", &EtcdVersionPair{&EtcdVersion{semver.MustParse("10.100.1000")}, storageEtcd3}, true},
- }
-
- for _, c := range cases {
- vp, err := ParseEtcdVersionPair(c.versionTxt)
- if err != nil {
- t.Errorf("Failed to parse '%s': %v", c.versionTxt, err)
- }
- if vp.Equals(c.version) != c.match {
- t.Errorf("Expected '%s' to be parsed as '%+v', got '%+v'", c.versionTxt, c.version, vp)
- }
- if vp.String() != c.versionTxt {
- t.Errorf("Expected round trip serialization back to '%s', got '%s'", c.versionTxt, vp.String())
- }
- }
-
- unparsables := []string{
- "1.1/etcd3",
- "1.1.1.1/etcd3",
- "1.1.1/etcd4",
- }
- for _, unparsable := range unparsables {
- vp, err := ParseEtcdVersionPair(unparsable)
- if err == nil {
- t.Errorf("Should have failed to parse '%s' but got '%s'", unparsable, vp)
- }
- }
-}
-
-func TestMajorMinorEquals(t *testing.T) {
- cases := []struct {
- first *EtcdVersion
- second *EtcdVersion
- match bool
- }{
- {&EtcdVersion{semver.Version{Major: 3, Minor: 1, Patch: 2}}, &EtcdVersion{semver.Version{Major: 3, Minor: 1, Patch: 0}}, true},
- {&EtcdVersion{semver.Version{Major: 3, Minor: 1, Patch: 2}}, &EtcdVersion{semver.Version{Major: 3, Minor: 1, Patch: 2}}, true},
-
- {&EtcdVersion{semver.Version{Major: 3, Minor: 0, Patch: 0}}, &EtcdVersion{semver.Version{Major: 3, Minor: 1, Patch: 0}}, false},
- {&EtcdVersion{semver.Version{Major: 2, Minor: 0, Patch: 0}}, &EtcdVersion{semver.Version{Major: 3, Minor: 0, Patch: 0}}, false},
- }
-
- for _, c := range cases {
- if c.first.MajorMinorEquals(c.second) != c.match {
- t.Errorf("Expected (%+v == %+v) == %t, got %t", c.first, c.second, c.match, !c.match)
- }
- }
-}