mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-01-11 15:30:34 +00:00
Compare commits
4 Commits
9033_fix
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fc1c70028 | ||
|
|
56a08a4d69 | ||
|
|
6e0469e7f3 | ||
|
|
100541d9c2 |
2
.github/workflows/pr.yml
vendored
2
.github/workflows/pr.yml
vendored
@@ -1,5 +1,5 @@
|
||||
name: Pull Request CI Check
|
||||
on: [push, pull_request]
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
|
||||
build:
|
||||
|
||||
34
.github/workflows/push.yml
vendored
Normal file
34
.github/workflows/push.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Master CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Set up Go 1.14
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.14
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Build
|
||||
run: make local
|
||||
|
||||
- name: Test
|
||||
run: make test
|
||||
|
||||
- name: Publish container image
|
||||
run: |
|
||||
docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
|
||||
./hack/docker-push.sh
|
||||
24
.travis.yml
24
.travis.yml
@@ -1,24 +0,0 @@
|
||||
dist: xenial
|
||||
language: go
|
||||
go:
|
||||
- 1.14.x
|
||||
sudo: required
|
||||
services:
|
||||
- docker
|
||||
script:
|
||||
- export BRANCH=$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then echo $TRAVIS_BRANCH;
|
||||
else echo $TRAVIS_PULL_REQUEST_BRANCH; fi)
|
||||
- echo "TRAVIS_BRANCH=$TRAVIS_BRANCH, PR=$TRAVIS_PULL_REQUEST, BRANCH=$BRANCH"
|
||||
- hack/ci-check.sh
|
||||
deploy:
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
script: hack/docker-push.sh
|
||||
on:
|
||||
repo: vmware-tanzu/velero
|
||||
all_branches: true
|
||||
env:
|
||||
global:
|
||||
# generated using `travis encrypt`: https://docs.travis-ci.com/user/environment-variables#encrypting-environment-variables
|
||||
- secure: Sa0R7bhOvrybIWvURjEpOLQ3/v1IqiSM2uwYTSMhLtwHunkiYXpbXi/wVPkujM7kgnFeJzGfNTZI6blkl3Vnz6Ca48avxiv+KRk7bNlTn/e2LkJaqOh/NcwqrVyWG8cZpWTHAzwJkHrV6xcWRTWFqx/UhUHH4PdmXd0pj3/DDxHhUZXJkZfYDC2uI+kmhJd9x1yxxz6OTXrGeMw22R5RtgltcQYjYWjGhXjNJ96+0QhC6juCwwtuU3oz7vfV1dP6ea9yeiHKCdDR0xp/Ymtlt4DULgQv4vuVBCR7LuPjsdLI1QslKbCz+vk/XwEcckf3R+yJUaArGLSM0f3AwuAZnETwkfKu9gJCEz8Xvvwsy5QHj/DGeSr+JY4Ylb1YzJG48R5QMcNwOgQOpewamBggxkyyOlNanUGzMJ8F7zjDOZjOUsRSSG+cn01/oJEHNyFmNkxbugOGuxqpi8qIfyWrdPSb8AdnUVUi2GDR/unSm2uZLSJtjaeypqzxalFdL04pbkk6/vKZTi6VMIyewHeR0XIC/VKQ+av2CwjnfqTtTHQHMh2kVxVzy4SKPYgWvbUUg9SG0z2lmfBWzE1HS+ozUKruxBu/YnPHaFz3D/+w0AwtHSYp3FMkCC/2SZU1hiOzAKX+S8T5K1OKoBiIEEUVa90EnSslCIiNIUHXgdtG62U=
|
||||
- secure: MAHgHvxxPJKxcECwdjaTa/ZW2wlhfcpcprXZqPJKQVFS6S5Wc+xf0CBRggnZT/aEs52EpD2bZy505a4uy9EcTDeq7q5oaXbOBFdyKgB1leWiycup60p6BmVqre5//EzXe+CJpkSu46ajcVORJIEGjs0tS5p0HV70sqpsfd13L2pXpmZ54RIj+TOm7Tz6VVeSNUmQ7WymIrvKg4cQ50w+aS7ak+gy8b6oYKWX01oZPQZA3W1pvYcBgBKobiQN8dh7WvT6QmTu/38WykTjt+9CfEJOboitgEESMjf06ueaLrhQoKQafH3XzfkfEW7PWWGl1RZYMEf15Joj1edIW1i9eYZ1T3fvBaIwvgc7lLgNdzc5ZxG8eEtoDSfB0+gDJOPBLCo+5G3xvRCie7OkKvXdijkCKmc+FRMJlayHkEw75cgId9svDEJghtt2VlWAM4ba4S79Og+cyI0VROkzS0FEjabz74tU9haVaypVnLmzEC/BhilzCt/jhRuYInpsMBeasGQ6Rg8gjos4AQamdaXJRI520o7zFWXVYqctfPr+mrVOYCfUISnSS3uqb4UlvLBqXDPVWBBIgmPrSgvHXxbjsajFuyWTULm60nj/JUuh7BC9HF9pil3g4/2E9cpLZCFQxVYu6+30eEv4dCO7Ptq3XIINDWbbS6Pmw62/ZsE8ABU=
|
||||
8
Makefile
8
Makefile
@@ -40,6 +40,10 @@ CLI_PLATFORMS ?= linux-amd64 linux-arm linux-arm64 darwin-amd64 windows-amd64 li
|
||||
CONTAINER_PLATFORMS ?= linux-amd64 linux-ppc64le linux-arm linux-arm64
|
||||
MANIFEST_PLATFORMS ?= amd64 ppc64le arm arm64
|
||||
|
||||
# set git sha and tree state
|
||||
GIT_SHA = $(shell git rev-parse HEAD)
|
||||
GIT_DIRTY = $(shell git status --porcelain 2> /dev/null)
|
||||
|
||||
###
|
||||
### These variables should not need tweaking.
|
||||
###
|
||||
@@ -113,6 +117,8 @@ local: build-dirs
|
||||
VERSION=$(VERSION) \
|
||||
PKG=$(PKG) \
|
||||
BIN=$(BIN) \
|
||||
GIT_SHA=$(GIT_SHA) \
|
||||
GIT_DIRTY="$(GIT_DIRTY)" \
|
||||
OUTPUT_DIR=$$(pwd)/_output/bin/$(GOOS)/$(GOARCH) \
|
||||
./hack/build.sh
|
||||
|
||||
@@ -126,6 +132,8 @@ _output/bin/$(GOOS)/$(GOARCH)/$(BIN): build-dirs
|
||||
VERSION=$(VERSION) \
|
||||
PKG=$(PKG) \
|
||||
BIN=$(BIN) \
|
||||
GIT_SHA=$(GIT_SHA) \
|
||||
GIT_DIRTY=\"$(GIT_DIRTY)\" \
|
||||
OUTPUT_DIR=/output/$(GOOS)/$(GOARCH) \
|
||||
./hack/build.sh'"
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@ If you are ready to jump in and test, add code, or help with documentation, foll
|
||||
|
||||
See [the list of releases][6] to find out about feature changes.
|
||||
|
||||
[1]: https://travis-ci.org/vmware-tanzu/velero.svg?branch=master
|
||||
[2]: https://travis-ci.org/vmware-tanzu/velero
|
||||
[1]: https://github.com/vmware-tanzu/velero/workflows/Master%20CI/badge.svg
|
||||
[2]: https://github.com/vmware-tanzu/velero/actions?query=workflow%3A"Master+CI"
|
||||
[4]: https://github.com/vmware-tanzu/velero/issues
|
||||
[6]: https://github.com/vmware-tanzu/velero/releases
|
||||
[9]: https://kubernetes.io/docs/setup/
|
||||
|
||||
@@ -1,3 +1,48 @@
|
||||
## v1.4.3
|
||||
### 2020-10-20
|
||||
|
||||
### Download
|
||||
https://github.com/vmware-tanzu/velero/releases/tag/v1.4.3
|
||||
|
||||
### Container Image
|
||||
`velero/velero:v1.4.3`
|
||||
|
||||
### Documentation
|
||||
https://velero.io/docs/v1.4/
|
||||
|
||||
### Upgrading
|
||||
https://velero.io/docs/v1.4/upgrade-to-1.4/
|
||||
|
||||
### All Changes
|
||||
* Restore CRD Resource name to fix CRD wait functionality. (#2949, @sseago)
|
||||
* rename the PV if VolumeSnapshotter has modified the PV name (#2835, @pawanpraka1)
|
||||
* Ensure that bound PVCs and PVs remain bound on restore. (#3007, @nrb)
|
||||
|
||||
## v1.4.2
|
||||
### 2020-07-13
|
||||
|
||||
### Download
|
||||
https://github.com/vmware-tanzu/velero/releases/tag/v1.4.2
|
||||
|
||||
### Container Image
|
||||
`velero/velero:v1.4.2`
|
||||
|
||||
### Documentation
|
||||
https://velero.io/docs/v1.4/
|
||||
|
||||
### Upgrading
|
||||
https://velero.io/docs/v1.4/upgrade-to-1.4/
|
||||
|
||||
### All Changes
|
||||
* log a warning instead of erroring if an additional item returned from a plugin can't be found in the Kubernetes API (#2595, @skriss)
|
||||
* Adjust restic default time out to 4 hours and base pod resource requests to 500m CPU/512Mi memory. (#2696, @nrb)
|
||||
* capture version of the CRD prior before invoking the remap_crd_version backup item action (#2683, @ashish-amarnath)
|
||||
|
||||
|
||||
## v1.4.1
|
||||
|
||||
This tag was created in code, but has no associated docker image due to misconfigured building infrastructure. v1.4.2 fixes this.
|
||||
|
||||
## v1.4.0
|
||||
### 2020-05-26
|
||||
|
||||
|
||||
@@ -39,10 +39,13 @@ if [ -z "${VERSION}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${GIT_SHA}" ]; then
|
||||
echo "GIT_SHA must be set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export CGO_ENABLED=0
|
||||
|
||||
GIT_SHA=$(git rev-parse HEAD)
|
||||
GIT_DIRTY=$(git status --porcelain 2> /dev/null)
|
||||
if [[ -z "${GIT_DIRTY}" ]]; then
|
||||
GIT_TREE_STATE=clean
|
||||
else
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
|
||||
set +x
|
||||
|
||||
if [[ -z "$TRAVIS" ]]; then
|
||||
echo "This script is intended to be run only on Travis." >&2
|
||||
if [[ -z "$CI" ]]; then
|
||||
echo "This script is intended to be run only on Github Actions." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -47,37 +47,43 @@ function highest_release() {
|
||||
done
|
||||
}
|
||||
|
||||
triggeredBy=$(echo $GITHUB_REF | cut -d / -f 2)
|
||||
if [[ "$triggeredBy" == "heads" ]]; then
|
||||
BRANCH=$(echo $GITHUB_REF | cut -d / -f 3)
|
||||
TAG=
|
||||
elif [[ "$triggeredBy" == "tags" ]]; then
|
||||
BRANCH=
|
||||
TAG=$(echo $GITHUB_REF | cut -d / -f 3)
|
||||
fi
|
||||
|
||||
if [[ "$BRANCH" == "master" ]]; then
|
||||
VERSION="$BRANCH"
|
||||
elif [[ ! -z "$TRAVIS_TAG" ]]; then
|
||||
# Tags aren't fetched by Travis on checkout, and we don't need them for master
|
||||
elif [[ ! -z "$TAG" ]]; then
|
||||
# Explicitly checkout tags when building from a git tag.
|
||||
# This is not needed when building from master
|
||||
git fetch --tags
|
||||
# Calculate the latest release if there's a tag.
|
||||
highest_release
|
||||
VERSION="$TRAVIS_TAG"
|
||||
VERSION="$TAG"
|
||||
else
|
||||
# If we're not on master and we're not building a tag, exit early.
|
||||
echo "We're not on master and we're not building a tag, exit early."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
# Assume we're not tagging `latest` by default, and never on master.
|
||||
TAG_LATEST=false
|
||||
if [[ "$BRANCH" == "master" ]]; then
|
||||
echo "Building master, not tagging latest."
|
||||
elif [[ "$TRAVIS_TAG" == "$HIGHEST" ]]; then
|
||||
elif [[ "$TAG" == "$HIGHEST" ]]; then
|
||||
TAG_LATEST=true
|
||||
fi
|
||||
|
||||
# Debugging info
|
||||
echo "Highest tag found: $HIGHEST"
|
||||
echo "BRANCH: $BRANCH"
|
||||
echo "TRAVIS_TAG: $TRAVIS_TAG"
|
||||
echo "TAG: $TAG"
|
||||
echo "TAG_LATEST: $TAG_LATEST"
|
||||
|
||||
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
||||
unset GIT_HTTP_USER_AGENT
|
||||
|
||||
echo "Building and pushing container images."
|
||||
|
||||
VERSION="$VERSION" TAG_LATEST="$TAG_LATEST" make all-containers all-push all-manifests
|
||||
|
||||
75
hack/release-tools/chk_version.go
Normal file
75
hack/release-tools/chk_version.go
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
Copyright 2020 the Velero contributors.
|
||||
|
||||
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"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// This regex should match both our GA format (example: v1.4.3) and pre-release format (v1.2.4-beta.2)
|
||||
// The following sub-capture groups are defined:
|
||||
// major
|
||||
// minor
|
||||
// patch
|
||||
// prerelease (this will be alpha/beta followed by a ".", followed by 1 or more digits (alpha.5)
|
||||
var release_regex *regexp.Regexp = regexp.MustCompile("^v(?P<major>[[:digit:]]+)\\.(?P<minor>[[:digit:]]+)\\.(?P<patch>[[:digit:]]+)(-{1}(?P<prerelease>(alpha|beta)\\.[[:digit:]]+))*")
|
||||
|
||||
// This small program exists because checking the VELERO_VERSION rules in bash is difficult, and difficult to test for correctness.
|
||||
// Calling it with --verify will verify whether or not the VELERO_VERSION environment variable is a valid version string, without parsing for its components.
|
||||
// Calling it without --verify will try to parse the version into its component pieces.
|
||||
func main() {
|
||||
|
||||
velero_version := os.Getenv("VELERO_VERSION")
|
||||
|
||||
submatches := reSubMatchMap(release_regex, velero_version)
|
||||
|
||||
// Didn't match the regex, exit.
|
||||
if len(submatches) == 0 {
|
||||
fmt.Printf("VELERO_VERSION of %s was not valid. Please correct the value and retry.", velero_version)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(os.Args) > 1 && os.Args[1] == "--verify" {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Send these in a bash variable format to stdout, so that they can be consumed by bash scripts that call the go program.
|
||||
fmt.Printf("VELERO_MAJOR=%s\n", submatches["major"])
|
||||
fmt.Printf("VELERO_MINOR=%s\n", submatches["minor"])
|
||||
fmt.Printf("VELERO_PATCH=%s\n", submatches["patch"])
|
||||
fmt.Printf("VELERO_PRERELEASE=%s\n", submatches["prerelease"])
|
||||
}
|
||||
|
||||
// reSubMatchMap returns a map with the named submatches within a regular expression populated as keys, and their matched values within a given string as values.
|
||||
// If no matches are found, a nil map is returned
|
||||
func reSubMatchMap(r *regexp.Regexp, s string) map[string]string {
|
||||
match := r.FindStringSubmatch(s)
|
||||
submatches := make(map[string]string)
|
||||
if len(match) == 0 {
|
||||
return submatches
|
||||
}
|
||||
for i, name := range r.SubexpNames() {
|
||||
// 0 will always be empty from the return values of SubexpNames's documentation, so skip it.
|
||||
if i != 0 {
|
||||
submatches[name] = match[i]
|
||||
}
|
||||
}
|
||||
|
||||
return submatches
|
||||
}
|
||||
71
hack/release-tools/chk_version_test.go
Normal file
71
hack/release-tools/chk_version_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright 2020 the Velero contributors.
|
||||
|
||||
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"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRegexMatching(t *testing.T) {
|
||||
tests := []struct {
|
||||
version string
|
||||
expectMatch bool
|
||||
}{
|
||||
{
|
||||
version: "v1.4.0",
|
||||
expectMatch: true,
|
||||
},
|
||||
{
|
||||
version: "v2.0.0",
|
||||
expectMatch: true,
|
||||
},
|
||||
{
|
||||
version: "v1.5.0-alpha.1",
|
||||
expectMatch: true,
|
||||
},
|
||||
{
|
||||
version: "v1.16.1320-beta.14",
|
||||
expectMatch: true,
|
||||
},
|
||||
{
|
||||
version: "1.0.0",
|
||||
expectMatch: false,
|
||||
},
|
||||
{
|
||||
// this is true because while the "--" is invalid, v1.0.0 is a valid part of the regex
|
||||
version: "v1.0.0--beta.1",
|
||||
expectMatch: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
name := fmt.Sprintf("Testing version string %s", test.version)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
results := reSubMatchMap(release_regex, test.version)
|
||||
|
||||
if len(results) == 0 && test.expectMatch {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if len(results) > 0 && !test.expectMatch {
|
||||
fmt.Printf("%v", results)
|
||||
t.Fail()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
118
hack/release-tools/tag-release.sh
Executable file
118
hack/release-tools/tag-release.sh
Executable file
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2020 the Velero contributors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
# This script will do the necessary checks and actions to create a release of Velero.
|
||||
# It will first validate that all prerequisites are met, then verify the version string is what the user expects.
|
||||
# A git tag will be created and pushed to GitHub, and GoReleaser will be invoked.
|
||||
|
||||
# This script is meant to be a combination of documentation and executable.
|
||||
# If you have questions at any point, please stop and ask!
|
||||
|
||||
# Directory in which the script itself resides, so we can use it for calling programs that are in the same directory.
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
function tag_and_push() {
|
||||
echo "Tagging and pushing $VELERO_VERSION"
|
||||
git tag $VELERO_VERSION
|
||||
git push $VELERO_VERSION
|
||||
}
|
||||
|
||||
# For now, have the person doing the release pass in the VELERO_VERSION variable as an environment variable.
|
||||
# In the future, we might be able to inspect git via `git describe --abbrev=0` to get a hint for it.
|
||||
if [[ -z "$VELERO_VERSION" ]]; then
|
||||
printf "The \$VELERO_VERSION environment variable is not set. Please set it with\n\texport VELERO_VERSION=v<version.to.release>\nthen try again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Make sure the user's provided their github token, so we can give it to goreleaser.
|
||||
if [[ -z "$GITHUB_TOKEN" ]]; then
|
||||
printf "The GITHUB_TOKEN environment variable is not set. Please set it with\n\t export GITHUB_TOKEN=<your github token>\n then try again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
Ensure that we have a clean working tree before we let any changes happen, especially important for cutting release branches.
|
||||
if [[ -n $(git status --short) ]]; then
|
||||
echo "Your git working directory is dirty! Please clean up untracked files and stash any changes before proceeding."
|
||||
exit 3
|
||||
fi
|
||||
|
||||
# Make sure that there's no issue with the environment variable's format before trying to eval the parsed version.
|
||||
if ! go run $DIR/chk_version.go --verify; then
|
||||
exit 2
|
||||
fi
|
||||
# Since we're past the validation of the VELERO_VERSION, parse the version's individual components.
|
||||
eval $(go run $DIR/chk_version.go)
|
||||
|
||||
|
||||
printf "To clarify, you've provided a version string of $VELERO_VERSION.\n"
|
||||
printf "Based on this, the following assumptions have been made: \n"
|
||||
|
||||
[[ "$VELERO_PATCH" != 0 ]] && printf "*\t This is a patch release.\n"
|
||||
|
||||
# -n is "string is non-empty"
|
||||
[[ -n $VELERO_PRERELEASE ]] && printf "*\t This is a pre-release.\n"
|
||||
|
||||
# -z is "string is empty"
|
||||
[[ -z $VELERO_PRERELEASE ]] && printf "*\t This is a GA release.\n"
|
||||
|
||||
echo "If this is all correct, press enter/return to proceed to TAG THE RELEASE and UPLOAD THE TAG TO GITHUB."
|
||||
echo "Otherwise, press ctrl-c to CANCEL the process without making any changes."
|
||||
|
||||
read -p "Ready to continue? "
|
||||
|
||||
echo "Alright, let's go."
|
||||
|
||||
echo "Pulling down all git tags and branches before doing any work."
|
||||
git fetch upstream --all --tags
|
||||
|
||||
# If we've got a patch release, we'll need to create a release branch for it.
|
||||
if [[ "$VELERO_PATCH" > 0 ]]; then
|
||||
release_branch_name=release-$VELERO_MAJOR.$VELERO_MINOR
|
||||
|
||||
# Check if the branch exists, creating it if not.
|
||||
# The fetch command above should have gotten all the upstream branches, so we can safely assume this check is local & upstream branches.
|
||||
if [[ -z $(git branch | grep $release_branch_name) ]]; then
|
||||
git checkout -b $release_branch_name
|
||||
echo "Release branch made."
|
||||
else
|
||||
echo "Release branch $release_branch_name exists already."
|
||||
git checkout $release_branch_name
|
||||
fi
|
||||
|
||||
echo "Now you'll need to cherry-pick any relevant git commits into this release branch."
|
||||
echo "Either pause this script with ctrl-z, or open a new terminal window and do the cherry-picking."
|
||||
read -p "Press enter when you're done cherry-picking. THIS WILL MAKE A TAG PUSH THE BRANCH TO UPSTREAM"
|
||||
|
||||
# TODO can/should we add a way to review the cherry-picked commits before the push?
|
||||
|
||||
echo "Pushing $release_branch_name to upstream remote"
|
||||
git push --set-upstream upstream/$release_branch_name $release_branch_name
|
||||
|
||||
tag_and_push
|
||||
else
|
||||
echo "Checking out upstream/master."
|
||||
git checkout upstream/master
|
||||
|
||||
tag_and_push
|
||||
fi
|
||||
|
||||
|
||||
|
||||
echo "Invoking Goreleaser to create the GitHub release."
|
||||
RELEASE_NOTES_FILE=changelogs/CHANGELOG-$VELERO_MAJOR.$VELERO_MINOR.md \
|
||||
PUBLISH=TRUE \
|
||||
make release
|
||||
@@ -1723,7 +1723,7 @@ func TestBackupActionAdditionalItems(t *testing.T) {
|
||||
},
|
||||
|
||||
{
|
||||
name: "if there's an error backing up additional items, the item the action was run for isn't backed up",
|
||||
name: "if additional items aren't found in the API, they're skipped and the original item is still backed up",
|
||||
backup: defaultBackup().Result(),
|
||||
apiResources: []*test.APIResource{
|
||||
test.Pods(
|
||||
@@ -1746,8 +1746,10 @@ func TestBackupActionAdditionalItems(t *testing.T) {
|
||||
},
|
||||
},
|
||||
want: []string{
|
||||
"resources/pods/namespaces/ns-1/pod-1.json",
|
||||
"resources/pods/namespaces/ns-2/pod-2.json",
|
||||
"resources/pods/namespaces/ns-3/pod-3.json",
|
||||
"resources/pods/v1-preferredversion/namespaces/ns-1/pod-1.json",
|
||||
"resources/pods/v1-preferredversion/namespaces/ns-2/pod-2.json",
|
||||
"resources/pods/v1-preferredversion/namespaces/ns-3/pod-3.json",
|
||||
},
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
@@ -159,6 +160,12 @@ func (ib *itemBackupper) backupItem(logger logrus.FieldLogger, obj runtime.Unstr
|
||||
}
|
||||
}
|
||||
|
||||
// capture the version of the object before invoking plugin actions as the plugin may update
|
||||
// the group version of the object.
|
||||
// group version of this object
|
||||
// Used on filepath to backup up all groups and versions
|
||||
version := resourceVersion(obj)
|
||||
|
||||
updatedObj, err := ib.executeActions(log, obj, groupResource, name, namespace, metadata)
|
||||
if err != nil {
|
||||
backupErrs = append(backupErrs, err)
|
||||
@@ -203,10 +210,6 @@ func (ib *itemBackupper) backupItem(logger logrus.FieldLogger, obj runtime.Unstr
|
||||
return false, kubeerrs.NewAggregate(backupErrs)
|
||||
}
|
||||
|
||||
// group version of this object
|
||||
// Used on filepath to backup up all groups and versions
|
||||
version := resourceVersion(obj)
|
||||
|
||||
// Getting the preferred group version of this resource
|
||||
preferredVersion := preferredGVR.Version
|
||||
|
||||
@@ -249,6 +252,7 @@ func (ib *itemBackupper) backupItem(logger logrus.FieldLogger, obj runtime.Unstr
|
||||
|
||||
// backing up the preferred version backup without API Group version on path - this is for backward compability
|
||||
|
||||
log.Debugf("Resource %s/%s, version= %s, preferredVersion=%s", groupResource.String(), name, version, preferredVersion)
|
||||
if version == preferredVersion {
|
||||
if namespace != "" {
|
||||
filePath = filepath.Join(velerov1api.ResourcesDir, groupResource.String(), velerov1api.NamespaceScopedDir, namespace, name+".json")
|
||||
@@ -271,7 +275,6 @@ func (ib *itemBackupper) backupItem(logger logrus.FieldLogger, obj runtime.Unstr
|
||||
if _, err := ib.tarWriter.Write(itemBytes); err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true, nil
|
||||
@@ -339,12 +342,20 @@ func (ib *itemBackupper) executeActions(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
additionalItem, err := client.Get(additionalItem.Name, metav1.GetOptions{})
|
||||
item, err := client.Get(additionalItem.Name, metav1.GetOptions{})
|
||||
if apierrors.IsNotFound(err) {
|
||||
log.WithFields(logrus.Fields{
|
||||
"groupResource": additionalItem.GroupResource,
|
||||
"namespace": additionalItem.Namespace,
|
||||
"name": additionalItem.Name,
|
||||
}).Warnf("Additional item was not found in Kubernetes API, can't back it up")
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if _, err = ib.backupItem(log, additionalItem, gvr.GroupResource(), gvr); err != nil {
|
||||
if _, err = ib.backupItem(log, item, gvr.GroupResource(), gvr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/kuberesource"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
)
|
||||
|
||||
@@ -110,7 +109,7 @@ func fetchV1beta1CRD(name string, betaCRDClient apiextv1beta1client.CustomResour
|
||||
// See https://github.com/kubernetes/kubernetes/issues/3030. Unsure why this is happening here and not in main Velero;
|
||||
// probably has to do with List calls and Dynamic client vs typed client
|
||||
// Set these all the time, since they shouldn't ever be different, anyway
|
||||
betaCRD.Kind = kuberesource.CustomResourceDefinitions.Resource
|
||||
betaCRD.Kind = "CustomResourceDefinition"
|
||||
betaCRD.APIVersion = apiextv1beta1.SchemeGroupVersion.String()
|
||||
|
||||
m, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&betaCRD)
|
||||
|
||||
@@ -170,6 +170,7 @@ func TestRemapCRDVersionActionData(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "apiextensions.k8s.io/v1beta1", item.UnstructuredContent()["apiVersion"])
|
||||
assert.Equal(t, crd.Kind, item.GetObjectKind().GroupVersionKind().GroupKind().Kind)
|
||||
name, _, err := unstructured.NestedString(item.UnstructuredContent(), "metadata", "name")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, crd.Name, name)
|
||||
|
||||
@@ -75,7 +75,7 @@ const (
|
||||
defaultMetricsAddress = ":8085"
|
||||
|
||||
defaultBackupSyncPeriod = time.Minute
|
||||
defaultPodVolumeOperationTimeout = 60 * time.Minute
|
||||
defaultPodVolumeOperationTimeout = 240 * time.Minute
|
||||
defaultResourceTerminatingTimeout = 10 * time.Minute
|
||||
|
||||
// server's client default qps and burst
|
||||
|
||||
@@ -70,11 +70,9 @@ func DescribeBackup(
|
||||
}
|
||||
}
|
||||
|
||||
if status.Phase == velerov1api.BackupPhasePartiallyFailed {
|
||||
d.Println()
|
||||
d.Printf("Errors:\t%d\n", status.Errors)
|
||||
d.Printf("Warnings:\t%d\n", status.Warnings)
|
||||
}
|
||||
d.Println()
|
||||
d.Printf("Errors:\t%d\n", status.Errors)
|
||||
d.Printf("Warnings:\t%d\n", status.Warnings)
|
||||
|
||||
d.Println()
|
||||
DescribeBackupSpec(d, backup.Spec)
|
||||
|
||||
@@ -35,6 +35,8 @@ var (
|
||||
// https://github.com/kubernetes/kubernetes/blob/v1.15.3/pkg/printers/tableprinter.go#L204
|
||||
{Name: "Name", Type: "string", Format: "name"},
|
||||
{Name: "Status"},
|
||||
{Name: "Errors"},
|
||||
{Name: "Warnings"},
|
||||
{Name: "Created"},
|
||||
{Name: "Expires"},
|
||||
{Name: "Storage Location"},
|
||||
@@ -58,7 +60,6 @@ func printBackupList(list *velerov1api.BackupList) []metav1.TableRow {
|
||||
var timestampSuffix = regexp.MustCompile("-[0-9]{14}$")
|
||||
|
||||
func sortBackupsByPrefixAndTimestamp(list *velerov1api.BackupList) {
|
||||
|
||||
sort.Slice(list.Items, func(i, j int) bool {
|
||||
iSuffixIndex := timestampSuffix.FindStringIndex(list.Items[i].Name)
|
||||
jSuffixIndex := timestampSuffix.FindStringIndex(list.Items[j].Name)
|
||||
@@ -98,18 +99,17 @@ func printBackup(backup *velerov1api.Backup) []metav1.TableRow {
|
||||
if backup.DeletionTimestamp != nil && !backup.DeletionTimestamp.Time.IsZero() {
|
||||
status = "Deleting"
|
||||
}
|
||||
if status == string(velerov1api.BackupPhasePartiallyFailed) {
|
||||
if backup.Status.Errors == 1 {
|
||||
status = fmt.Sprintf("%s (1 error)", status)
|
||||
} else {
|
||||
status = fmt.Sprintf("%s (%d errors)", status, backup.Status.Errors)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
location := backup.Spec.StorageLocation
|
||||
|
||||
row.Cells = append(row.Cells, backup.Name, status, backup.Status.StartTimestamp, humanReadableTimeFromNow(expiration), location, metav1.FormatLabelSelector(backup.Spec.LabelSelector))
|
||||
row.Cells = append(row.Cells,
|
||||
backup.Name,
|
||||
status,
|
||||
backup.Status.Errors,
|
||||
backup.Status.Warnings,
|
||||
backup.Status.StartTimestamp,
|
||||
humanReadableTimeFromNow(expiration),
|
||||
backup.Spec.StorageLocation,
|
||||
metav1.FormatLabelSelector(backup.Spec.LabelSelector),
|
||||
)
|
||||
|
||||
return []metav1.TableRow{row}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ var (
|
||||
{Name: "Name", Type: "string", Format: "name"},
|
||||
{Name: "Backup"},
|
||||
{Name: "Status"},
|
||||
{Name: "Warnings"},
|
||||
{Name: "Errors"},
|
||||
{Name: "Warnings"},
|
||||
{Name: "Created"},
|
||||
{Name: "Selector"},
|
||||
}
|
||||
@@ -60,8 +60,8 @@ func printRestore(restore *v1.Restore) []metav1.TableRow {
|
||||
restore.Name,
|
||||
restore.Spec.BackupName,
|
||||
status,
|
||||
restore.Status.Warnings,
|
||||
restore.Status.Errors,
|
||||
restore.Status.Warnings,
|
||||
restore.CreationTimestamp.Time,
|
||||
metav1.FormatLabelSelector(restore.Spec.LabelSelector),
|
||||
)
|
||||
|
||||
@@ -46,10 +46,10 @@ var (
|
||||
DefaultVeleroPodMemRequest = "128Mi"
|
||||
DefaultVeleroPodCPULimit = "1000m"
|
||||
DefaultVeleroPodMemLimit = "256Mi"
|
||||
DefaultResticPodCPURequest = "0"
|
||||
DefaultResticPodMemRequest = "0"
|
||||
DefaultResticPodCPULimit = "0"
|
||||
DefaultResticPodMemLimit = "0"
|
||||
DefaultResticPodCPURequest = "500m"
|
||||
DefaultResticPodMemRequest = "512Mi"
|
||||
DefaultResticPodCPULimit = "1000m"
|
||||
DefaultResticPodMemLimit = "1Gi"
|
||||
)
|
||||
|
||||
func labels() map[string]string {
|
||||
|
||||
@@ -47,20 +47,6 @@ func (r *pvRestorer) executePVAction(obj *unstructured.Unstructured) (*unstructu
|
||||
return nil, errors.New("PersistentVolume is missing its name")
|
||||
}
|
||||
|
||||
// It's simpler to just access the spec through the unstructured object than to convert
|
||||
// to structured and back here, especially since the SetVolumeID(...) call below needs
|
||||
// the unstructured representation (and does a conversion internally).
|
||||
res, ok := obj.Object["spec"]
|
||||
if !ok {
|
||||
return nil, errors.New("spec not found")
|
||||
}
|
||||
spec, ok := res.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, errors.Errorf("spec was of type %T, expected map[string]interface{}", res)
|
||||
}
|
||||
|
||||
delete(spec, "claimRef")
|
||||
|
||||
if boolptr.IsSetToFalse(r.snapshotVolumes) {
|
||||
// The backup had snapshots disabled, so we can return early
|
||||
return obj, nil
|
||||
|
||||
@@ -56,19 +56,6 @@ func TestExecutePVAction_NoSnapshotRestores(t *testing.T) {
|
||||
restore: builder.ForRestore(api.DefaultNamespace, "").Result(),
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "no spec should error",
|
||||
obj: NewTestUnstructured().WithName("pv-1").Unstructured,
|
||||
restore: builder.ForRestore(api.DefaultNamespace, "").Result(),
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "ensure spec.claimRef is deleted",
|
||||
obj: NewTestUnstructured().WithName("pv-1").WithAnnotations("a", "b").WithSpec("claimRef", "someOtherField").Unstructured,
|
||||
restore: builder.ForRestore(api.DefaultNamespace, "").RestorePVs(false).Result(),
|
||||
backup: defaultBackup().Phase(api.BackupPhaseInProgress).Result(),
|
||||
expectedRes: NewTestUnstructured().WithAnnotations("a", "b").WithName("pv-1").WithSpec("someOtherField").Unstructured,
|
||||
},
|
||||
{
|
||||
name: "ensure spec.storageClassName is retained",
|
||||
obj: NewTestUnstructured().WithName("pv-1").WithAnnotations("a", "b").WithSpec("storageClassName", "someOtherField").Unstructured,
|
||||
@@ -81,7 +68,7 @@ func TestExecutePVAction_NoSnapshotRestores(t *testing.T) {
|
||||
obj: NewTestUnstructured().WithName("pv-1").WithAnnotations("a", "b").WithSpec("claimRef", "storageClassName", "someOtherField").Unstructured,
|
||||
restore: builder.ForRestore(api.DefaultNamespace, "").RestorePVs(true).Result(),
|
||||
backup: defaultBackup().Phase(api.BackupPhaseInProgress).SnapshotVolumes(false).Result(),
|
||||
expectedRes: NewTestUnstructured().WithName("pv-1").WithAnnotations("a", "b").WithSpec("storageClassName", "someOtherField").Unstructured,
|
||||
expectedRes: NewTestUnstructured().WithName("pv-1").WithAnnotations("a", "b").WithSpec("claimRef", "storageClassName", "someOtherField").Unstructured,
|
||||
},
|
||||
{
|
||||
name: "restore.spec.restorePVs=false, return early",
|
||||
|
||||
@@ -60,6 +60,14 @@ import (
|
||||
"github.com/vmware-tanzu/velero/pkg/volume"
|
||||
)
|
||||
|
||||
// These annotations are taken from the Kubernetes persistent volume/persistent volume claim controller.
|
||||
// They cannot be directly importing because they are part of the kubernetes/kubernetes package, and importing that package is unsupported.
|
||||
// Their values are well-known and slow changing. They're duplicated here as constants to provide compile-time checking.
|
||||
// Originals can be found in kubernetes/kubernetes/pkg/controller/volume/persistentvolume/util/util.go.
|
||||
const KubeAnnBindCompleted = "pv.kubernetes.io/bind-completed"
|
||||
const KubeAnnBoundByController = "pv.kubernetes.io/bound-by-controller"
|
||||
const KubeAnnDynamicallyProvisioned = "pv.kubernetes.io/provisioned-by"
|
||||
|
||||
type VolumeSnapshotterGetter interface {
|
||||
GetVolumeSnapshotter(name string) (velero.VolumeSnapshotter, error)
|
||||
}
|
||||
@@ -868,6 +876,13 @@ func (ctx *context) restoreItem(obj *unstructured.Unstructured, groupResource sc
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
// Check to see if the claimRef.namespace field needs to be remapped, and do so if necessary.
|
||||
_, err = remapClaimRefNS(ctx, obj)
|
||||
if err != nil {
|
||||
errs.Add(namespace, err)
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
var shouldRestoreSnapshot bool
|
||||
if !shouldRenamePV {
|
||||
// Check if the PV exists in the cluster before attempting to create
|
||||
@@ -885,6 +900,9 @@ func (ctx *context) restoreItem(obj *unstructured.Unstructured, groupResource sc
|
||||
}
|
||||
|
||||
if shouldRestoreSnapshot {
|
||||
// reset the PV's binding status so that Kubernetes can properly associate it with the restored PVC.
|
||||
obj = resetVolumeBindingInfo(obj)
|
||||
|
||||
// even if we're renaming the PV, obj still has the old name here, because the pvRestorer
|
||||
// uses the original name to look up metadata about the snapshot.
|
||||
ctx.log.Infof("Restoring persistent volume from snapshot.")
|
||||
@@ -894,6 +912,11 @@ func (ctx *context) restoreItem(obj *unstructured.Unstructured, groupResource sc
|
||||
return warnings, errs
|
||||
}
|
||||
obj = updatedObj
|
||||
|
||||
// VolumeSnapshotter has modified the PV name, we should rename the PV
|
||||
if oldName != obj.GetName() {
|
||||
shouldRenamePV = true
|
||||
}
|
||||
}
|
||||
|
||||
if shouldRenamePV {
|
||||
@@ -939,8 +962,9 @@ func (ctx *context) restoreItem(obj *unstructured.Unstructured, groupResource sc
|
||||
default:
|
||||
ctx.log.Infof("Restoring persistent volume as-is because it doesn't have a snapshot and its reclaim policy is not Delete.")
|
||||
|
||||
// we call the pvRestorer here to clear out the PV's claimRef, so it can be re-claimed
|
||||
// when its PVC is restored.
|
||||
obj = resetVolumeBindingInfo(obj)
|
||||
// we call the pvRestorer here to clear out the PV's claimRef.UID, so it can be re-claimed
|
||||
// when its PVC is restored and gets a new UID.
|
||||
updatedObj, err := ctx.pvRestorer.executePVAction(obj)
|
||||
if err != nil {
|
||||
errs.Add(namespace, fmt.Errorf("error executing PVAction for %s: %v", resourceID, err))
|
||||
@@ -1033,17 +1057,16 @@ func (ctx *context) restoreItem(obj *unstructured.Unstructured, groupResource sc
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
if pvc.Spec.VolumeName != "" && ctx.pvsToProvision.Has(pvc.Spec.VolumeName) {
|
||||
ctx.log.Infof("Resetting PersistentVolumeClaim %s/%s for dynamic provisioning", namespace, name)
|
||||
if pvc.Spec.VolumeName != "" {
|
||||
// This used to only happen with restic volumes, but now always remove this binding metadata
|
||||
obj = resetVolumeBindingInfo(obj)
|
||||
|
||||
// use the unstructured helpers here since we're only deleting and
|
||||
// the unstructured converter will add back (empty) fields for metadata
|
||||
// and status that we removed earlier.
|
||||
unstructured.RemoveNestedField(obj.Object, "spec", "volumeName")
|
||||
annotations := obj.GetAnnotations()
|
||||
delete(annotations, "pv.kubernetes.io/bind-completed")
|
||||
delete(annotations, "pv.kubernetes.io/bound-by-controller")
|
||||
obj.SetAnnotations(annotations)
|
||||
// This is the case for restic volumes, where we need to actually have an empty volume created instead of restoring one.
|
||||
// The assumption is that any PV in pvsToProvision doesn't have an associated snapshot.
|
||||
if ctx.pvsToProvision.Has(pvc.Spec.VolumeName) {
|
||||
ctx.log.Infof("Resetting PersistentVolumeClaim %s/%s for dynamic provisioning", namespace, name)
|
||||
unstructured.RemoveNestedField(obj.Object, "spec", "volumeName")
|
||||
}
|
||||
}
|
||||
|
||||
if newName, ok := ctx.renamedPVs[pvc.Spec.VolumeName]; ok {
|
||||
@@ -1192,6 +1215,40 @@ func shouldRenamePV(ctx *context, obj *unstructured.Unstructured, client client.
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// remapClaimRefNS remaps a PersistentVolume's claimRef.Namespace based on a restore's NamespaceMappings, if necessary.
|
||||
// Returns true if the namespace was remapped, false if it was not required.
|
||||
func remapClaimRefNS(ctx *context, obj *unstructured.Unstructured) (bool, error) {
|
||||
if len(ctx.restore.Spec.NamespaceMapping) == 0 {
|
||||
ctx.log.Debug("Persistent volume does not need to have the claimRef.namespace remapped because restore is not remapping any namespaces")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Conversion to the real type here is more readable than all the error checking involved with reading each field individually.
|
||||
pv := new(v1.PersistentVolume)
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, pv); err != nil {
|
||||
return false, errors.Wrapf(err, "error converting persistent volume to structured")
|
||||
}
|
||||
|
||||
if pv.Spec.ClaimRef == nil {
|
||||
ctx.log.Debugf("Persistent volume does not need to have the claimRef.namepace remapped because it's not claimed")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
targetNS, ok := ctx.restore.Spec.NamespaceMapping[pv.Spec.ClaimRef.Namespace]
|
||||
|
||||
if !ok {
|
||||
ctx.log.Debugf("Persistent volume does not need to have the claimRef.namespace remapped because it's not claimed by a PVC in a namespace that's being remapped")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
err := unstructured.SetNestedField(obj.Object, targetNS, "spec", "claimRef", "namespace")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
ctx.log.Debug("Persistent volume's namespace was updated")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// restorePodVolumeBackups restores the PodVolumeBackups for the given restored pod
|
||||
func restorePodVolumeBackups(ctx *context, createdObj *unstructured.Unstructured, originalNamespace string) {
|
||||
if ctx.resticRestorer == nil {
|
||||
@@ -1269,6 +1326,29 @@ func hasDeleteReclaimPolicy(obj map[string]interface{}) bool {
|
||||
return policy == string(v1.PersistentVolumeReclaimDelete)
|
||||
}
|
||||
|
||||
// resetVolumeBindingInfo clears any necessary metadata out of a PersistentVolume or PersistentVolumeClaim that would make it ineligible to be re-bound by Velero.
|
||||
func resetVolumeBindingInfo(obj *unstructured.Unstructured) *unstructured.Unstructured {
|
||||
// Clean out ClaimRef UID and resourceVersion, since this information is highly unique.
|
||||
unstructured.RemoveNestedField(obj.Object, "spec", "claimRef", "uid")
|
||||
unstructured.RemoveNestedField(obj.Object, "spec", "claimRef", "resourceVersion")
|
||||
|
||||
// Clear out any annotations used by the Kubernetes PV controllers to track bindings.
|
||||
annotations := obj.GetAnnotations()
|
||||
|
||||
// Upon restore, this new PV will look like a statically provisioned, manually-bound volume rather than one bound by the controller, so remove the annotation that signals that a controller bound it.
|
||||
delete(annotations, KubeAnnBindCompleted)
|
||||
// Remove the annotation that signals that the PV is already bound; we want the PV(C) controller to take the two objects and bind them again.
|
||||
delete(annotations, KubeAnnBoundByController)
|
||||
|
||||
// Remove the provisioned-by annotation which signals that the persistent volume was dynamically provisioned; it is now statically provisioned.
|
||||
delete(annotations, KubeAnnDynamicallyProvisioned)
|
||||
|
||||
// GetAnnotations returns a copy, so we have to set them again
|
||||
obj.SetAnnotations(annotations)
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
func resetMetadataAndStatus(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||||
res, ok := obj.Object["metadata"]
|
||||
if !ok {
|
||||
|
||||
@@ -1834,7 +1834,7 @@ func TestRestorePersistentVolumes(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "when a PV with a reclaim policy of retain has no snapshot and does not exist in-cluster, it gets restored, without its claim ref",
|
||||
name: "when a PV with a reclaim policy of retain has no snapshot and does not exist in-cluster, it gets restored, with its claim ref",
|
||||
restore: defaultRestore().Result(),
|
||||
backup: defaultBackup().Result(),
|
||||
tarball: newTarWriter(t).
|
||||
@@ -1853,6 +1853,7 @@ func TestRestorePersistentVolumes(t *testing.T) {
|
||||
ObjectMeta(
|
||||
builder.WithLabels("velero.io/backup-name", "backup-1", "velero.io/restore-name", "restore-1"),
|
||||
).
|
||||
ClaimRef("ns-1", "pvc-1").
|
||||
Result(),
|
||||
),
|
||||
},
|
||||
@@ -2100,13 +2101,12 @@ func TestRestorePersistentVolumes(t *testing.T) {
|
||||
want: []*test.APIResource{
|
||||
test.PVs(
|
||||
builder.ForPersistentVolume("source-pv").AWSEBSVolumeID("source-volume").ClaimRef("source-ns", "pvc-1").Result(),
|
||||
// note that the renamed PV is not expected to have a claimRef in this test; that would be
|
||||
// added after creation by the Kubernetes PV/PVC controller when it does a bind.
|
||||
builder.ForPersistentVolume("renamed-source-pv").
|
||||
ObjectMeta(
|
||||
builder.WithAnnotations("velero.io/original-pv-name", "source-pv"),
|
||||
builder.WithLabels("velero.io/backup-name", "backup-1", "velero.io/restore-name", "restore-1"),
|
||||
).
|
||||
// the namespace for this PV's claimRef should be the one that the PVC was remapped into.
|
||||
).ClaimRef("target-ns", "pvc-1").
|
||||
AWSEBSVolumeID("new-volume").
|
||||
Result(),
|
||||
),
|
||||
@@ -2165,6 +2165,7 @@ func TestRestorePersistentVolumes(t *testing.T) {
|
||||
ObjectMeta(
|
||||
builder.WithLabels("velero.io/backup-name", "backup-1", "velero.io/restore-name", "restore-1"),
|
||||
).
|
||||
ClaimRef("target-ns", "pvc-1").
|
||||
AWSEBSVolumeID("new-volume").
|
||||
Result(),
|
||||
),
|
||||
@@ -2178,6 +2179,67 @@ func TestRestorePersistentVolumes(t *testing.T) {
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "when a PV is renamed and the original PV does not exist in-cluster, the PV should be renamed",
|
||||
restore: defaultRestore().NamespaceMappings("source-ns", "target-ns").Result(),
|
||||
backup: defaultBackup().Result(),
|
||||
tarball: newTarWriter(t).
|
||||
addItems(
|
||||
"persistentvolumes",
|
||||
builder.ForPersistentVolume("source-pv").AWSEBSVolumeID("source-volume").ClaimRef("source-ns", "pvc-1").Result(),
|
||||
).
|
||||
addItems(
|
||||
"persistentvolumeclaims",
|
||||
builder.ForPersistentVolumeClaim("source-ns", "pvc-1").VolumeName("source-pv").Result(),
|
||||
).
|
||||
done(),
|
||||
apiResources: []*test.APIResource{
|
||||
test.PVs(),
|
||||
test.PVCs(),
|
||||
},
|
||||
volumeSnapshots: []*volume.Snapshot{
|
||||
{
|
||||
Spec: volume.SnapshotSpec{
|
||||
BackupName: "backup-1",
|
||||
Location: "default",
|
||||
PersistentVolumeName: "source-pv",
|
||||
},
|
||||
Status: volume.SnapshotStatus{
|
||||
Phase: volume.SnapshotPhaseCompleted,
|
||||
ProviderSnapshotID: "snapshot-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
volumeSnapshotLocations: []*velerov1api.VolumeSnapshotLocation{
|
||||
builder.ForVolumeSnapshotLocation(velerov1api.DefaultNamespace, "default").Provider("provider-1").Result(),
|
||||
},
|
||||
volumeSnapshotterGetter: map[string]velero.VolumeSnapshotter{
|
||||
"provider-1": &volumeSnapshotter{
|
||||
snapshotVolumes: map[string]string{"snapshot-1": "new-pvname"},
|
||||
pvName: map[string]string{"new-pvname": "new-pvname"},
|
||||
},
|
||||
},
|
||||
want: []*test.APIResource{
|
||||
test.PVs(
|
||||
builder.ForPersistentVolume("new-pvname").
|
||||
ObjectMeta(
|
||||
builder.WithLabels("velero.io/backup-name", "backup-1", "velero.io/restore-name", "restore-1"),
|
||||
builder.WithAnnotations("velero.io/original-pv-name", "source-pv"),
|
||||
).
|
||||
ClaimRef("target-ns", "pvc-1").
|
||||
AWSEBSVolumeID("new-pvname").
|
||||
Result(),
|
||||
),
|
||||
test.PVCs(
|
||||
builder.ForPersistentVolumeClaim("target-ns", "pvc-1").
|
||||
ObjectMeta(
|
||||
builder.WithLabels("velero.io/backup-name", "backup-1", "velero.io/restore-name", "restore-1"),
|
||||
).
|
||||
VolumeName("new-pvname").
|
||||
Result(),
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "when a PV with a reclaim policy of retain has a snapshot and exists in-cluster, neither the snapshot nor the PV are restored",
|
||||
restore: defaultRestore().Result(),
|
||||
@@ -2284,13 +2346,12 @@ func TestRestorePersistentVolumes(t *testing.T) {
|
||||
want: []*test.APIResource{
|
||||
test.PVs(
|
||||
builder.ForPersistentVolume("source-pv").AWSEBSVolumeID("source-volume").ClaimRef("source-ns", "pvc-1").Result(),
|
||||
// note that the renamed PV is not expected to have a claimRef in this test; that would be
|
||||
// added after creation by the Kubernetes PV/PVC controller when it does a bind.
|
||||
builder.ForPersistentVolume("volumesnapshotter-renamed-source-pv").
|
||||
ObjectMeta(
|
||||
builder.WithAnnotations("velero.io/original-pv-name", "source-pv"),
|
||||
builder.WithLabels("velero.io/backup-name", "backup-1", "velero.io/restore-name", "restore-1"),
|
||||
).
|
||||
ClaimRef("target-ns", "pvc-1").
|
||||
AWSEBSVolumeID("new-volume").
|
||||
Result(),
|
||||
),
|
||||
@@ -2870,3 +2931,49 @@ func (h *harness) addItems(t *testing.T, resource *test.APIResource) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_resetVolumeBindingInfo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
obj *unstructured.Unstructured
|
||||
expected *unstructured.Unstructured
|
||||
}{
|
||||
{
|
||||
name: "PVs that are bound have their binding and dynamic provisioning annotations removed",
|
||||
obj: NewTestUnstructured().WithMetadataField("kind", "persistentVolume").
|
||||
WithName("pv-1").WithAnnotations(
|
||||
KubeAnnBindCompleted,
|
||||
KubeAnnBoundByController,
|
||||
KubeAnnDynamicallyProvisioned,
|
||||
).WithSpecField("claimRef", map[string]interface{}{
|
||||
"namespace": "ns-1",
|
||||
"name": "pvc-1",
|
||||
"uid": "abc",
|
||||
"resourceVersion": "1"}).Unstructured,
|
||||
expected: NewTestUnstructured().WithMetadataField("kind", "persistentVolume").
|
||||
WithName("pv-1").
|
||||
WithAnnotations().
|
||||
WithSpecField("claimRef", map[string]interface{}{
|
||||
"namespace": "ns-1", "name": "pvc-1"}).Unstructured,
|
||||
},
|
||||
{
|
||||
name: "PVCs that are bound have their binding annotations removed, but the volume name stays",
|
||||
obj: NewTestUnstructured().WithMetadataField("kind", "persistentVolumeClaim").
|
||||
WithName("pvc-1").WithAnnotations(
|
||||
KubeAnnBindCompleted,
|
||||
KubeAnnBoundByController,
|
||||
KubeAnnDynamicallyProvisioned,
|
||||
).WithSpecField("volumeName", "pv-1").Unstructured,
|
||||
expected: NewTestUnstructured().WithMetadataField("kind", "persistentVolumeClaim").
|
||||
WithName("pvc-1").WithAnnotations().
|
||||
WithSpecField("volumeName", "pv-1").Unstructured,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
actual := resetVolumeBindingInfo(tc.obj)
|
||||
assert.Equal(t, tc.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ If you are ready to jump in and test, add code, or help with documentation, foll
|
||||
|
||||
See [the list of releases][6] to find out about feature changes.
|
||||
|
||||
[1]: https://travis-ci.org/vmware-tanzu/velero.svg?branch=master
|
||||
[2]: https://travis-ci.org/vmware-tanzu/velero
|
||||
[1]: https://github.com/vmware-tanzu/velero/workflows/Master%20CI/badge.svg
|
||||
[2]: https://github.com/vmware-tanzu/velero/actions?query=workflow%3A"Master+CI"
|
||||
|
||||
[4]: https://github.com/vmware-tanzu/velero/issues
|
||||
[6]: https://github.com/vmware-tanzu/velero/releases
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
# Release Instructions
|
||||
|
||||
## Ahead of Time
|
||||
|
||||
### (GA Only) Release Blog Post PR
|
||||
|
||||
Prepare a PR containing the release blog post. It's usually easiest to make a copy of the most recent existing post, then replace the content as appropriate.
|
||||
|
||||
You also need to update `site/index.html` to have "Latest Release Information" contain a link to the new post.
|
||||
|
||||
### (Pre-Release and GA) Changelog and Docs PR
|
||||
|
||||
1. In a branch, create the file `changelogs/CHANGELOG-<major>.<minor>.md` (if it doesn't already exist) by copying the most recent one.
|
||||
1. Run `make changelog` to generate a list of all unreleased changes. Copy/paste the output into `CHANGELOG-<major>.<minor>.md`, under the "All Changes" section for the release.
|
||||
- You *may* choose to tweak formatting on the list of changes by adding code blocks, etc.
|
||||
1. (GA Only) Remove all changelog files from `changelogs/unreleased`.
|
||||
1. Update the main `CHANGELOG.md` file to properly reference the release-specific changelog file:
|
||||
- (Pre-Release) List the release under "Development release"
|
||||
- (GA) List the release under "Current release", remove any pre-releases from "Development release", and move the previous release into "Older releases".
|
||||
1. If there is an existing set of pre-release versioned docs for the version you are releasing (i.e. `site/docs/v1.4-pre` exists, and you're releasing `v1.4.0-beta.2` or `v1.4.0`):
|
||||
- Remove the directory containing the pre-release docs, i.e. `site/docs/<pre-release-version>`.
|
||||
- Delete the pre-release docs table of contents file, i.e. `site/_data/<pre-release-version>-toc.yml`.
|
||||
- Remove the pre-release docs table of contents mapping entry from `site/_data/toc-mapping.yml`.
|
||||
- Remove all references to the pre-release docs from `site/_config.yml`.
|
||||
1. Run `NEW_DOCS_VERSION=v<major.minor> VELERO_VERSION=v<full-version> make gen-docs` (e.g. `NEW_DOCS_VERSION=v1.2 VELERO_VERSION=v1.2.0 make gen-docs` or `NEW_DOCS_VERSION=v1.2-pre VELERO_VERSION=v1.2.0-beta.1 make gen-docs`).
|
||||
- Note that:
|
||||
- **NEW_DOCS_VERSION** defines the version that the docs will be tagged with (i.e. what's in the URL, what shows up in the version dropdown on the site). This should be formatted as either `v1.4` (for a GA release), or `v1.4-pre` (for an alpha/beta/RC).
|
||||
- **VELERO_VERSION** defines the tag of Velero that any `https://github.com/vmware-tanzu/velero/...` links in the docs should redirect to.
|
||||
1. Follow the additional instructions at `site/README-JEKYLL.md` to complete the docs generation process.
|
||||
1. Do a review of the diffs, and/or run `make serve-docs` and review the site.
|
||||
1. Submit a PR containing the changelog and the version-tagged docs.
|
||||
|
||||
### (Pre-Release and GA) GitHub Token
|
||||
|
||||
To run the `goreleaser` process to generate a GitHub release, you'll need to have a GitHub token. See https://goreleaser.com/environment/ for more details.
|
||||
|
||||
You may regenerate the token for every release if you prefer.
|
||||
|
||||
#### If you don't already have a token
|
||||
1. Go to https://github.com/settings/tokens/new.
|
||||
1. Choose a name for your token.
|
||||
1. Check the "repo" scope.
|
||||
1. Click "Generate token".
|
||||
1. Save the token value somewhere - you'll need it during the release, in the `GITHUB_TOKEN` environment variable.
|
||||
|
||||
#### If you do already have a token, but need to regenerate it
|
||||
1. Go to https://github.com/settings/tokens.
|
||||
1. Click on the name of the relevant token.
|
||||
1. Click "Regenerate token".
|
||||
1. Save the token value somewhere - you'll need it during the release, in the `GITHUB_TOKEN` environment variable.
|
||||
|
||||
## During Release
|
||||
|
||||
This process is the same for both pre-release and GA, except for the fact that there will not be a blog post PR to merge for pre-release versions.
|
||||
|
||||
1. Merge the changelog + docs PR, so that it's included in the release tag.
|
||||
1. Make sure your working directory is clean: `git status` should show `nothing to commit, working tree clean`.
|
||||
1. Run `git fetch upstream master && git checkout upstream/master`.
|
||||
1. Run `git tag <VERSION>` (e.g. `git tag v1.2.0` or `git tag v1.2.0-beta.1`).
|
||||
1. Run `git push upstream <VERSION>` (e.g. `git push upstream v1.2.0` or `git push upstream v1.2.0-beta.1`). This will trigger the Travis CI job that builds/publishes the Docker images.
|
||||
1. Generate the GitHub release (it will be created in "Draft" status, which means it's not visible to the outside world until you click "Publish"):
|
||||
|
||||
```bash
|
||||
GITHUB_TOKEN=your-github-token \
|
||||
RELEASE_NOTES_FILE=changelogs/CHANGELOG-<major>.<minor>.md \
|
||||
PUBLISH=true \
|
||||
make release
|
||||
```
|
||||
|
||||
1. Navigate to the draft GitHub release, at https://github.com/vmware-tanzu/velero/releases.
|
||||
1. If this is a patch release (e.g. `v1.2.1`), note that the full `CHANGELOG-1.2.md` contents will be included in the body of the GitHub release. You need to delete the previous releases' content (e.g. `v1.2.0`'s changelog) so that only the latest patch release's changelog shows.
|
||||
1. Do a quick review for formatting. **Note:** the `goreleaser` process should detect if it's a pre-release version, and check that box in the GitHub release appropriately, but it's always worth double-checking.
|
||||
1. Publish the release.
|
||||
1. By now, the Docker images should have been published. Perform a smoke-test - for example:
|
||||
- Download the CLI from the GitHub release
|
||||
- Use it to install Velero into a cluster (or manually update an existing deployment to use the new images)
|
||||
- Verify that `velero version` shows the expected output
|
||||
- Run a backup/restore and ensure it works
|
||||
1. (GA Only) Merge the blog post PR.
|
||||
1. Announce the release:
|
||||
- Twitter (mention a few highlights, link to the blog post)
|
||||
- Slack channel
|
||||
- Google group (this doesn't get a lot of traffic, and recent releases may not have been posted here)
|
||||
@@ -33,8 +33,8 @@ If you are ready to jump in and test, add code, or help with documentation, foll
|
||||
|
||||
See [the list of releases][6] to find out about feature changes.
|
||||
|
||||
[1]: https://travis-ci.org/vmware-tanzu/velero.svg?branch=master
|
||||
[2]: https://travis-ci.org/vmware-tanzu/velero
|
||||
[1]: https://github.com/vmware-tanzu/velero/workflows/Master%20CI/badge.svg
|
||||
[2]: https://github.com/vmware-tanzu/velero/actions?query=workflow%3A"Master+CI"
|
||||
|
||||
[4]: https://github.com/vmware-tanzu/velero/issues
|
||||
[6]: https://github.com/vmware-tanzu/velero/releases
|
||||
|
||||
Reference in New Issue
Block a user