diff --git a/.github/workflows/crds-verify-kind.yaml b/.github/workflows/crds-verify-kind.yaml index a7383a98bc..e098364613 100644 --- a/.github/workflows/crds-verify-kind.yaml +++ b/.github/workflows/crds-verify-kind.yaml @@ -14,7 +14,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.18 + go-version: 1.20.3 id: go # Look for a CLI that's made for this PR - name: Fetch built CLI @@ -57,15 +57,13 @@ jobs: matrix: # Latest k8s versions. There's no series-based tag, nor is there a latest tag. k8s: - - 1.16.15 - - 1.17.17 - - 1.18.15 - 1.19.7 - 1.20.2 - 1.21.1 - 1.22.0 - 1.23.6 - 1.24.2 + - 1.25.3 # All steps run in parallel unless otherwise specified. # See https://docs.github.com/en/actions/learn-github-actions/managing-complex-workflows#creating-dependent-jobs steps: @@ -83,7 +81,7 @@ jobs: velero-${{ github.event.pull_request.number }}- - uses: engineerd/setup-kind@v0.5.0 with: - version: "v0.14.0" + version: "v0.17.0" image: "kindest/node:v${{ matrix.k8s }}" - name: Install CRDs run: | diff --git a/.github/workflows/e2e-test-kind.yaml b/.github/workflows/e2e-test-kind.yaml index c41c7ff475..c96af0c218 100644 --- a/.github/workflows/e2e-test-kind.yaml +++ b/.github/workflows/e2e-test-kind.yaml @@ -14,7 +14,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.18 + go-version: 1.20.3 id: go # Look for a CLI that's made for this PR - name: Fetch built CLI @@ -60,23 +60,19 @@ jobs: strategy: matrix: k8s: - # doesn't cover 1.15 as 1.15 doesn't support "apiextensions.k8s.io/v1" that is needed for the case - #- 1.15.12 - - 1.16.15 - - 1.17.17 - - 1.18.20 - 1.19.16 - 1.20.15 - 1.21.12 - 1.22.9 - 1.23.6 - 1.24.0 + - 1.25.3 fail-fast: false steps: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.18 + go-version: 1.20.3 id: go - name: Check out the code uses: actions/checkout@v2 @@ -85,7 +81,7 @@ jobs: docker run -d --rm -p 9000:9000 -e "MINIO_ACCESS_KEY=minio" -e "MINIO_SECRET_KEY=minio123" -e "MINIO_DEFAULT_BUCKETS=bucket,additional-bucket" bitnami/minio:2021.6.17-debian-10-r7 - uses: engineerd/setup-kind@v0.5.0 with: - version: "v0.14.0" + version: "v0.17.0" image: "kindest/node:v${{ matrix.k8s }}" - name: Fetch built CLI id: cli-cache diff --git a/.github/workflows/pr-ci-check.yml b/.github/workflows/pr-ci-check.yml index 2b98f731fa..a7cec263d5 100644 --- a/.github/workflows/pr-ci-check.yml +++ b/.github/workflows/pr-ci-check.yml @@ -8,7 +8,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.18 + go-version: 1.20.3 id: go - name: Check out the code uses: actions/checkout@v2 diff --git a/.github/workflows/pr-containers.yml b/.github/workflows/pr-containers.yml new file mode 100644 index 0000000000..c4d21f6d45 --- /dev/null +++ b/.github/workflows/pr-containers.yml @@ -0,0 +1,37 @@ +name: build Velero containers on Dockerfile change + +on: + pull_request: + branches: + - 'main' + - 'release-**' + paths: + - 'Dockerfile' + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + name: Checkout + + - name: Set up QEMU + id: qemu + uses: docker/setup-qemu-action@v1 + with: + platforms: all + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v1 + with: + version: latest + + # Although this action also calls docker-push.sh, it is not triggered + # by push, so BRANCH and TAG are empty by default. docker-push.sh will + # only build Velero image without pushing. + - name: Make Velero container without pushing to registry. + if: github.repository == 'vmware-tanzu/velero' + run: | + ./hack/docker-push.sh \ No newline at end of file diff --git a/.github/workflows/push-builder.yml b/.github/workflows/push-builder.yml index 513937a44f..03d9ee62b3 100644 --- a/.github/workflows/push-builder.yml +++ b/.github/workflows/push-builder.yml @@ -2,7 +2,9 @@ name: build-image on: push: - branches: [ main ] + branches: + - 'main' + - 'release-**' paths: - 'hack/build-image/Dockerfile' diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index d00606acd5..2fa7e833da 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -18,11 +18,16 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.18 + go-version: 1.20.3 id: go - uses: actions/checkout@v3 + # Fix issue of setup-gcloud + - run: | + sudo apt-get install python2.7 + export CLOUDSDK_PYTHON="/usr/bin/python2" + - uses: google-github-actions/setup-gcloud@v0 with: version: '285.0.0' @@ -94,4 +99,7 @@ jobs: - name: Publish container image to GCR if: github.repository == 'vmware-tanzu/velero' run: | + sudo swapoff -a + sudo rm -f /mnt/swapfile + docker image prune -a --force REGISTRY=gcr.io/velero-gcp ./hack/docker-push.sh diff --git a/Dockerfile b/Dockerfile index 757b03c075..0aaffd14d3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,48 +11,68 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -FROM --platform=$BUILDPLATFORM golang:1.18 as builder-env + +# Velero binary build section +FROM --platform=$BUILDPLATFORM golang:1.20.3-bullseye as velero-builder ARG GOPROXY +ARG BIN ARG PKG ARG VERSION +ARG REGISTRY ARG GIT_SHA ARG GIT_TREE_STATE -ARG REGISTRY +ARG TARGETOS +ARG TARGETARCH +ARG TARGETVARIANT ENV CGO_ENABLED=0 \ GO111MODULE=on \ GOPROXY=${GOPROXY} \ + GOOS=${TARGETOS} \ + GOARCH=${TARGETARCH} \ + GOARM=${TARGETVARIANT} \ LDFLAGS="-X ${PKG}/pkg/buildinfo.Version=${VERSION} -X ${PKG}/pkg/buildinfo.GitSHA=${GIT_SHA} -X ${PKG}/pkg/buildinfo.GitTreeState=${GIT_TREE_STATE} -X ${PKG}/pkg/buildinfo.ImageRegistry=${REGISTRY}" WORKDIR /go/src/github.com/vmware-tanzu/velero COPY . /go/src/github.com/vmware-tanzu/velero -FROM --platform=$BUILDPLATFORM builder-env as builder +RUN mkdir -p /output/usr/bin && \ + export GOARM=$( echo "${GOARM}" | cut -c2-) && \ + go build -o /output/${BIN} \ + -ldflags "${LDFLAGS}" ${PKG}/cmd/${BIN} + +# Restic binary build section +FROM --platform=$BUILDPLATFORM golang:1.19.8-bullseye as restic-builder +ARG BIN ARG TARGETOS ARG TARGETARCH ARG TARGETVARIANT -ARG PKG -ARG BIN ARG RESTIC_VERSION -ENV GOOS=${TARGETOS} \ +env CGO_ENABLED=0 \ + GO111MODULE=on \ + GOPROXY=${GOPROXY} \ + GOOS=${TARGETOS} \ GOARCH=${TARGETARCH} \ GOARM=${TARGETVARIANT} +COPY . /go/src/github.com/vmware-tanzu/velero + RUN mkdir -p /output/usr/bin && \ - export GOARM=$( echo "${GOARM}" | cut -c2-) && \ - bash ./hack/build-restic.sh && \ - go build -o /output/${BIN} \ - -ldflags "${LDFLAGS}" ${PKG}/cmd/${BIN} + export GOARM=$(echo "${GOARM}" | cut -c2-) && \ + /go/src/github.com/vmware-tanzu/velero/hack/build-restic.sh -FROM gcr.io/distroless/base-debian11:nonroot +# Velero image packing section +FROM gcr.io/distroless/base-nossl-debian11@sha256:9523ef8cf054e23a81e722d231c6f604ab43a03c5b174b5c8386c78c0b6473d0 LABEL maintainer="Nolan Brubaker " -COPY --from=builder /output / +COPY --from=velero-builder /output / + +COPY --from=restic-builder /output / USER nonroot:nonroot diff --git a/Makefile b/Makefile index 1525a7b111..c27112a2c4 100644 --- a/Makefile +++ b/Makefile @@ -82,7 +82,7 @@ see: https://velero.io/docs/main/build-from-source/#making-images-and-updating-v endef # The version of restic binary to be downloaded -RESTIC_VERSION ?= 0.13.1 +RESTIC_VERSION ?= 0.15.0 CLI_PLATFORMS ?= linux-amd64 linux-arm linux-arm64 darwin-amd64 darwin-arm64 windows-amd64 linux-ppc64le BUILDX_PLATFORMS ?= $(subst -,/,$(ARCH)) @@ -120,7 +120,7 @@ build-%: all-build: $(addprefix build-, $(CLI_PLATFORMS)) -all-containers: container-builder-env +all-containers: @$(MAKE) --no-print-directory container @$(MAKE) --no-print-directory container BIN=velero-restore-helper @@ -178,20 +178,6 @@ shell: build-dirs build-env $(BUILDER_IMAGE) \ /bin/sh $(CMD) -container-builder-env: -ifneq ($(BUILDX_ENABLED), true) - $(error $(BUILDX_ERROR)) -endif - @docker buildx build \ - --target=builder-env \ - --build-arg=GOPROXY=$(GOPROXY) \ - --build-arg=PKG=$(PKG) \ - --build-arg=VERSION=$(VERSION) \ - --build-arg=GIT_SHA=$(GIT_SHA) \ - --build-arg=GIT_TREE_STATE=$(GIT_TREE_STATE) \ - --build-arg=REGISTRY=$(REGISTRY) \ - -f $(VELERO_DOCKERFILE) . - container: ifneq ($(BUILDX_ENABLED), true) $(error $(BUILDX_ERROR)) @@ -200,6 +186,7 @@ endif --output=type=$(BUILDX_OUTPUT_TYPE) \ --platform $(BUILDX_PLATFORMS) \ $(addprefix -t , $(IMAGE_TAGS)) \ + --build-arg=GOPROXY=$(GOPROXY) \ --build-arg=PKG=$(PKG) \ --build-arg=BIN=$(BIN) \ --build-arg=VERSION=$(VERSION) \ diff --git a/README.md b/README.md index 99ded77e9a..92e496d3b3 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ The following is a list of the supported Kubernetes versions for each Velero ver | Velero version | Expected Kubernetes version compatibility| Tested on Kubernetes version| |----------------|--------------------|--------------------| -| 1.10 | 1.16-latest | 1.21, 1.22, 1.23, 1.24 and 1.25 | +| 1.10 | 1.16-latest | 1.22.5, 1.23.8, 1.24.6 and 1.25.1 | | 1.9 | 1.16-latest | 1.20.5, 1.21.2, 1.22.5, 1.23, and 1.24 | | 1.8 | 1.16-latest | | | 1.6.3-1.7.1 | 1.12-latest || diff --git a/Tiltfile b/Tiltfile index 3ce9aedf76..7272285cc1 100644 --- a/Tiltfile +++ b/Tiltfile @@ -50,7 +50,7 @@ git_sha = str(local("git rev-parse HEAD", quiet = True, echo_off = True)).strip( tilt_helper_dockerfile_header = """ # Tilt image -FROM golang:1.18 as tilt-helper +FROM golang:1.20.3 as tilt-helper # Support live reloading with Tilt RUN wget --output-document /restart.sh --quiet https://raw.githubusercontent.com/windmilleng/rerun-process-wrapper/master/restart.sh && \ diff --git a/changelogs/CHANGELOG-0.9.md b/changelogs/CHANGELOG-0.9.md index 4b685b32e5..f29a0d28ea 100644 --- a/changelogs/CHANGELOG-0.9.md +++ b/changelogs/CHANGELOG-0.9.md @@ -154,7 +154,7 @@ * Skip completed jobs and pods when restoring (#463, @nrb) * Set namespace correctly when syncing backups from object storage (#472, @skriss) * When building on macOS, bind-mount volumes with delegated config (#478, @skriss) - * Add replica sets and daemonsets to cohabitating resources so they're not backed up twice (#482 #485, @skriss) + * Add replica sets and daemonsets to cohabiting resources so they're not backed up twice (#482 #485, @skriss) * Shut down the Ark server gracefully on SIGINT/SIGTERM (#483, @skriss) * Only back up resources that support GET and DELETE in addition to LIST and CREATE (#486, @nrb) * Show a better error message when trying to get an incomplete restore's logs (#496, @nrb) diff --git a/changelogs/CHANGELOG-1.10.md b/changelogs/CHANGELOG-1.10.md index afdadeda1d..5413eb87c3 100644 --- a/changelogs/CHANGELOG-1.10.md +++ b/changelogs/CHANGELOG-1.10.md @@ -1,5 +1,77 @@ - ## v1.10.0 -### 2022-11-11 +## v1.10.3 +### 2023-04-27 + +### Download +https://github.com/vmware-tanzu/velero/releases/tag/v1.10.3 + +### Container Image +`velero/velero:v1.10.3` + +### Documentation +https://velero.io/docs/v1.10/ + +### Upgrading +https://velero.io/docs/v1.10/upgrade-to-1.10/ + +### All changes + * Fix issue #6182. If pod is not running, don't treat it as an error, let it go and leave a warning. (#6188, @Lyndon-Li) + * Bump v1.10's Golang version to v1.20 (#6143, @blackpiglet) + * Ignore not found error during patching managedFields (#6135, @ywk253100) + * Fix issue #5972, don't assume errorField as error type when dealing with logger.WithError (#6086, @Lyndon-Li) + * Restore Services before Clusters (#6058, @ywk253100) + +## v1.10.2 +### 2023-02-22 + +### Download +https://github.com/vmware-tanzu/velero/releases/tag/v1.10.2 + +### Container Image +`velero/velero:v1.10.2` + +### Documentation +https://velero.io/docs/v1.10/ + +### Upgrading +https://velero.io/docs/v1.10/upgrade-to-1.10/ + +### All changes + * Update distroless image and fix CVE-2022-41717 for release-1.10 (#5891, @blackpiglet) + * Set Kopia IgnoreUnknownTypes in ErrorHandlingPolicy to True for ignoring backup unknown file type (#5890, @qiuiming-best) + * Add labels for velero installed namespace to support PSA. (#5888, @blackpiglet) + * Publish backupresults json to enhance error info during backups. (#5879, @anshulahuja98) + * Restore finalizer and managedFields of metadata during the restoration (#5877, @ywk253100) + * Add secret restore item action to handle service account token secret (#5869, @ywk253100) + * Correct PVB/PVR Failed Phase patching during startup (#5830, @kaovilai) + +## v1.10.1 +### 2023-01-19 + +### Download +https://github.com/vmware-tanzu/velero/releases/tag/v1.10.1 + +### Container Image +`velero/velero:v1.10.1` + +### Documentation +https://velero.io/docs/v1.10/ + +### Upgrading +https://velero.io/docs/v1.10/upgrade-to-1.10/ + +### All changes + * Fix Restic v0.14.0 HIGH grade CVEs. (#5817, @blackpiglet) + * Bump up golang net to fix CVE-2022-41721 (#5811, @Lyndon-Li) + * Bump up golang to 1.18.10 for Velero (#5780, @Lyndon-Li) + * Add PR container build action, which will not push image. Add GOARM parameter. Remove container-builder-env section. (#5770, @blackpiglet) + * Add Restic builder in Dockerfile, and keep the used built Golang image version in accordance with upstream Restic. (#5765, @blackpiglet) + * Fix issue 5696, check if the repo is still openable before running the prune and forget operation, if not, try to reconnect the repo (#5714, @Lyndon-Li) + * Fix error with Restic backup empty volumes (#5711, @qiuming-best) + * Prevent nil panic on exec restore hooks (#5708, @dymurray) + * Fix CVEs scanned by trivy (#5655, @qiuming-best) + +## v1.10.0 +### 2022-11-23 ### Download https://github.com/vmware-tanzu/velero/releases/tag/v1.10.0 @@ -20,20 +92,20 @@ In this release, we introduced the Unified Repository architecture to build a da In this release, we also deeply integrate Velero with Kopia, specifically, Kopia's uploader modules are isolated as a generic file system uploader; Kopia's repository modules are encapsulated as the unified backup repository. -For more information, refer to the design document. +For more information, refer to the [design document](https://github.com/vmware-tanzu/velero/blob/v1.10.0/design/unified-repo-and-kopia-integration/unified-repo-and-kopia-integration.md). #### File system backup refactor -Velero's file system backup (a.k.s. pod volume backup or formerly restic backup) is refactored as the first user of the Unified Repository architecture. Specifically, we added a new path, the Kopia path, besides the existing Restic path. While Restic path is still available and set as default, you can opt in Kopia path by specifying the uploader-type parameter at installation time. Meanwhile, you are free to restore from existing backups under either path, Velero dynamically switches to the correct path to process the restore. +Velero's file system backup (a.k.s. pod volume backup or formerly restic backup) is refactored as the first user of the Unified Repository architecture. Specifically, we added a new path, the Kopia path, besides the existing Restic path. While Restic path is still available and set as default, you can opt in Kopia path by specifying the `uploader-type` parameter at installation time. Meanwhile, you are free to restore from existing backups under either path, Velero dynamically switches to the correct path to process the restore. Because of the new path, we renamed some modules and parameters, refer to the Break Changes section for more details. -For more information, visit the file system backup document and v1.10 upgrade guide document. +For more information, visit the [file system backup document](https://velero.io/docs/v1.10/file-system-backup/) and [v1.10 upgrade guide document](https://velero.io/docs/v1.10/upgrade-to-1.10/). -Meanwhile, we've created a performance guide for both Restic path and Kopia path, which helps you to choose between the two paths and provides you the best practice to configure them under different scenarios. Please note that the results in the guide are based on our testing environments, you may get different results when testing in your own ones. For more information, visit the performance guide document. +Meanwhile, we've created a performance guide for both Restic path and Kopia path, which helps you to choose between the two paths and provides you the best practice to configure them under different scenarios. Please note that the results in the guide are based on our testing environments, you may get different results when testing in your own ones. For more information, visit the [performance guide document](https://velero.io/docs/v1.10/performance-guidance/). #### Plugin versioning V1 refactor In this release, Velero moves plugins BackupItemAction, RestoreItemAction and VolumeSnapshotterAction to version v1, this allows future plugin changes that do not support backward compatibility, so is a preparation for various complex tasks, for example, data movement tasks. -For more information, refer to the plugin versioning design document. +For more information, refer to the [plugin versioning design document](https://github.com/vmware-tanzu/velero/blob/v1.10.0/design/plugin-versioning.md). #### Refactor the controllers using Kubebuilder v3 In this release we continued our code modernization work, rewriting some controllers using Kubebuilder v3. This work is ongoing and we will continue to make progress in future releases. @@ -41,7 +113,7 @@ In this release we continued our code modernization work, rewriting some control #### Add credentials to volume snapshot locations In this release, we enabled dedicate credentials options to volume snapshot locations so that you can specify credentials per volume snapshot location as same as backup storage location. -For more information, please visit the locations document. +For more information, please visit the [locations document](https://velero.io/docs/v1.10/locations/). #### CSI snapshot enhancements In this release we added several changes to enhance the robustness of CSI snapshot procedures, for example, some protection code for error handling, and a mechanism to skip exclusion checks so that CSI snapshot works with various backup resource filters. @@ -49,8 +121,8 @@ In this release we added several changes to enhance the robustness of CSI snapsh #### Backup schedule pause/unpause In this release, Velero supports to pause/unpause a backup schedule during or after its creation. Specifically: -At creation time, you can specify "–paused" flag to "velero schedule create" command, if so, you will create a paused schedule that will not run until it is unpaused -After creation, you can run "velero schedule pause" or "velero schedule unpause" command to pause/unpause a schedule +At creation time, you can specify `–paused` flag to `velero schedule create` command, if so, you will create a paused schedule that will not run until it is unpaused +After creation, you can run `velero schedule pause` or `velero schedule unpause` command to pause/unpause a schedule #### Runtime and dependencies In order to fix CVEs, we changed Velero's runtime and dependencies as follows: @@ -63,35 +135,36 @@ Compile Restic (v0.13.1) with go 1.18.8 instead of packaging the official binary #### Breaking changes Due to file system backup refactor, below modules and parameters name have been changed in this release: -"restic" daemonset is renamed to "node-agent" -"resticRepository" CR is renamed to "backupRepository" -"velero restic repo" command is renamed to "velero repo" -"velero-restic-credentials" secret is renamed to "velero-repo-credentials" -"default-volumes-to-restic" parameter is renamed to default-volumes-to-fs-backup -"restic-timeout" parameter is renamed to "fs-backup-timeout" -"default-restic-prune-frequency" parameter is renamed to "default-repo-maintain-frequency" +`restic` daemonset is renamed to `node-agent` +`resticRepository` CR is renamed to `backupRepository` +`velero restic repo` command is renamed to `velero repo` +`velero-restic-credentials` secret is renamed to `velero-repo-credentials` +`default-volumes-to-restic` parameter is renamed to `default-volumes-to-fs-backup` +`restic-timeout` parameter is renamed to `fs-backup-timeout` +`default-restic-prune-frequency` parameter is renamed to `default-repo-maintain-frequency` #### Upgrade -Due to the major changes of file system backup, the old upgrade steps are not suitable any more. For the new upgrade steps, visit v1.10 upgrade guide document. +Due to the major changes of file system backup, the old upgrade steps are not suitable any more. For the new upgrade steps, visit [v1.10 upgrade guide document](https://velero.io/docs/v1.10/upgrade-to-1.10/). #### Limitations/Known issues -In this release, Kopia backup repository (so the Kopia path of file system backup) doesn't support self signed certificate for S3 compatible storage. To track this problem, refer to this Velero issue or Kopia issue. +In this release, Kopia backup repository (so the Kopia path of file system backup) doesn't support self signed certificate for S3 compatible storage. To track this problem, refer to this [Velero issue](https://github.com/vmware-tanzu/velero/issues/5123) or [Kopia issue](https://github.com/kopia/kopia/issues/1443). -Due to the code change in Velero, there will be some code change required in vSphere plugin, without which the functionality may be impacted. Therefore, if you are using vSphere plugin in your workflow, please hold the upgrade until the issue #485 is fixed in vSphere plugin. +Due to the code change in Velero, there will be some code change required in vSphere plugin, without which the functionality may be impacted. Therefore, if you are using vSphere plugin in your workflow, please hold the upgrade until the issue [#485](https://github.com/vmware-tanzu/velero-plugin-for-vsphere/issues/485) is fixed in vSphere plugin. ### All changes + * Restore ClusterBootstrap before Cluster otherwise a new default ClusterBootstrap object is create for the cluster (#5616, @ywk253100) * Add compile restic binary for CVE fix (#5574, @qiuming-best) * Fix controller problematic log output (#5572, @qiuming-best) * Enhance the restore priorities list to support specifying the low prioritized resources that need to be restored in the last (#5535, @ywk253100) * fix restic backup progress error (#5534, @qiuming-best) * fix restic backup failure with self-signed certification backend storage (#5526, @qiuming-best) * Add credential store in backup deletion controller to support VSL credential. (#5521, @blackpiglet) - * Fix issue 5505: the pod volume backups/restores except the first one fail under the kopia path if "AZURE_CLOUD_NAME" is specified (#5512, @lyndon) - * After Pod Volume Backup/Restore refactor, remove all the unreasonable appearance of "restic" word from documents (#5499, @lyndon) - * Refactor Pod Volume Backup/Restore doc to match the new behavior (#5484, @lyndon) + * Fix issue 5505: the pod volume backups/restores except the first one fail under the kopia path if "AZURE_CLOUD_NAME" is specified (#5512, @Lyndon-Li) + * After Pod Volume Backup/Restore refactor, remove all the unreasonable appearance of "restic" word from documents (#5499, @Lyndon-Li) + * Refactor Pod Volume Backup/Restore doc to match the new behavior (#5484, @Lyndon-Li) * Remove redundancy code block left by #5388. (#5483, @blackpiglet) - * Issue fix 5477: create the common way to support S3 compatible object storages that work for both Restic and Kopia; Keep the resticRepoPrefix parameter for compatibility (#5478, @lyndon) + * Issue fix 5477: create the common way to support S3 compatible object storages that work for both Restic and Kopia; Keep the resticRepoPrefix parameter for compatibility (#5478, @Lyndon-Li) * Update the k8s.io dependencies to 0.24.0. This also required an update to github.com/bombsimon/logrusr/v3. Removed the `WithClusterName` method @@ -99,24 +172,24 @@ as it is a "legacy field that was always cleared by the system and never used" as per upstream k8s https://github.com/kubernetes/apimachinery/blob/release-1.24/pkg/apis/meta/v1/types.go#L257-L259 (#5471, @kcboyle) * Add v1.10 velero upgrade doc (#5468, @qiuming-best) - * Upgrade velero docker image to use go 1.18 and upgrade golangci-lint to 1.45.0 (#5459, @lyndon) + * Upgrade velero docker image to use go 1.18 and upgrade golangci-lint to 1.45.0 (#5459, @Lyndon-Li) * Add VolumeSnapshot client back. (#5449, @blackpiglet) * Change subcommand `velero restic repo` to `velero repo` (#5446, @allenxu404) - * Remove irrational "Restic" names in Velero code after the PVBR refactor (#5444, @lyndon) + * Remove irrational "Restic" names in Velero code after the PVBR refactor (#5444, @Lyndon-Li) * moved RIA execute input/output structs back to velero package (#5441, @sseago) - * Rename Velero pod volume restore init helper from "velero-restic-restore-helper" to "velero-restore-helper" (#5432, @lyndon) + * Rename Velero pod volume restore init helper from "velero-restic-restore-helper" to "velero-restore-helper" (#5432, @Lyndon-Li) * Skip the exclusion check for additional resources returned by BIA (#5429, @reasonerjt) * Change B/R describe CLI to support Kopia (#5412, @allenxu404) * Add nil check before execution of csi snapshot delete (#5401, @shubham-pampattiwar) * update velero using klog to version v2.9.0 (#5396, @blackpiglet) * Fix Test_prepareBackupRequest_BackupStorageLocation UT failure. (#5394, @blackpiglet) - * Rename Velero daemonset from "restic" to "node-agent" (#5390, @lyndon) + * Rename Velero daemonset from "restic" to "node-agent" (#5390, @Lyndon-Li) * Add some corner cases checking for CSI snapshot in backup controller. (#5388, @blackpiglet) - * Fix issue 5386: Velero providers a full URL as the S3Url while the underlying minio client only accept the host part of the URL as the endpoint and the schema should be specified separately. (#5387, @lyndon) + * Fix issue 5386: Velero providers a full URL as the S3Url while the underlying minio client only accept the host part of the URL as the endpoint and the schema should be specified separately. (#5387, @Lyndon-Li) * Fix restore error with flag namespace-mappings (#5377, @qiuming-best) - * Pod Volume Backup/Restore Refactor: Rename parameters in CRDs and commands to remove "Restic" word (#5370, @lyndon) + * Pod Volume Backup/Restore Refactor: Rename parameters in CRDs and commands to remove "Restic" word (#5370, @Lyndon-Li) * Added backupController's UT to test the prepareBackupRequest() method BackupStorageLocation processing logic (#5362, @niulechuan) - * Fix a repoEnsurer problem introduced by the refactor - The repoEnsurer didn't check "" state of BackupRepository, as a result, the function GetBackupRepository always returns without an error even though the ensreReady is specified. (#5359, @lyndon) + * Fix a repoEnsurer problem introduced by the refactor - The repoEnsurer didn't check "" state of BackupRepository, as a result, the function GetBackupRepository always returns without an error even though the ensreReady is specified. (#5359, @Lyndon-Li) * Add E2E test for schedule backup (#5355, @danfengliu) * Add useOwnerReferencesInBackup field doc for schedule. (#5353, @cleverhu) * Clarify the help message for the default value of parameter --snapshot-volumes, when it's not set. (#5350, @blackpiglet) @@ -126,22 +199,22 @@ https://github.com/kubernetes/apimachinery/blob/release-1.24/pkg/apis/meta/v1/ty * Add opt-in and opt-out PersistentVolume backup to E2E tests (#5331, @danfengliu) * Cancel downloadRequest when timeout without downloadURL (#5329, @kaovilai) * Fix PVB finds wrong parent snapshot (#5322, @qiuming-best) - * Fix issue 4874 and 4752: check the daemonset pod is running in the node where the workload pod resides before running the PVB for the pod (#5319, @lyndon) + * Fix issue 4874 and 4752: check the daemonset pod is running in the node where the workload pod resides before running the PVB for the pod (#5319, @Lyndon-Li) * plugin versioning v1 refactor for VolumeSnapshotter (#5318, @sseago) * Change the status of restore to completed from partially failed when restore empty backup (#5314, @allenxu404) * RestoreItemAction v1 refactoring for plugin api versioning (#5312, @sseago) - * Refactor the repoEnsurer code to use controller runtime client and wrap some common BackupRepository operations to share with other modules (#5308, @lyndon) + * Refactor the repoEnsurer code to use controller runtime client and wrap some common BackupRepository operations to share with other modules (#5308, @Lyndon-Li) * Remove snapshot related lister, informer and client from backup controller. (#5299, @jxun) * Remove github.com/apex/log logger. (#5297, @blackpiglet) * change CSISnapshotTimeout from pointer to normal variables. (#5294, @cleverhu) * Optimize code for restore exists resources. (#5293, @cleverhu) * Add more detailed comments for labels columns. (#5291, @cleverhu) * Add backup status checking in schedule controller. (#5283, @blackpiglet) - * Add changes for problems/enhancements found during smoking test for Kopia pod volume backup/restore (#5282, @lyndon) + * Add changes for problems/enhancements found during smoking test for Kopia pod volume backup/restore (#5282, @Lyndon-Li) * Support pause/unpause schedules (#5279, @ywk253100) * plugin/clientmgmt refactoring for BackupItemAction v1 (#5271, @sseago) * Don't move velero v1 plugins to new proto dir (#5263, @sseago) - * Fill gaps for Kopia path of PVBR: integrate Repo Manager with Unified Repo; pass UploaderType to PVBR backupper and restorer; pass RepositoryType to BackupRepository controller and Repo Ensurer (#5259, @lyndon) + * Fill gaps for Kopia path of PVBR: integrate Repo Manager with Unified Repo; pass UploaderType to PVBR backupper and restorer; pass RepositoryType to BackupRepository controller and Repo Ensurer (#5259, @Lyndon-Li) * Add csiSnapshotTimeout for describe backup (#5252, @cleverhu) * equip gc controller with configurable frequency (#5248, @allenxu404) * Fix nil pointer panic when restoring StatefulSets (#5247, @divolgin) @@ -149,9 +222,8 @@ https://github.com/kubernetes/apimachinery/blob/release-1.24/pkg/apis/meta/v1/ty * Fix edge cases for already exists resources (#5239, @shubham-pampattiwar) * Check for empty ns list before checking nslist[0] (#5236, @sseago) * Remove reference to non-existent doc (#5234, @reasonerjt) - * Add changes for Kopia Integration: Kopia Lib - method implementation -Add changes to write Kopia Repository logs to Velero log (#5233, @lyndon) - * Add changes for Kopia Integration: Kopia Lib - initialize Kopia repo (#5231, @lyndon) + * Add changes for Kopia Integration: Kopia Lib - method implementation. Add changes to write Kopia Repository logs to Velero log (#5233, @Lyndon-Li) + * Add changes for Kopia Integration: Kopia Lib - initialize Kopia repo (#5231, @Lyndon-Li) * Uploader Implementation: Kopia backup and restore (#5221, @qiuming-best) * Migrate backup sync controller from code-generator to kubebuilder. (#5218, @jxun) * check vsc null pointer (#5217, @lilongfeng0902) @@ -159,22 +231,19 @@ Add changes to write Kopia Repository logs to Velero log (#5233, @lyndon) * Uploader Implementation: Restic backup and restore (#5214, @qiuming-best) * Add parameter "uploader-type" to velero server (#5212, @reasonerjt) * Add annotation "pv.kubernetes.io/migrated-to" for CSI checking. (#5181, @jxun) - * Add changes for Kopia Integration: Unified Repository Provider - method implementation (#5179, @lyndon) + * Add changes for Kopia Integration: Unified Repository Provider - method implementation (#5179, @Lyndon-Li) * Treat namespaces with exclude label as excludedNamespaces Related issue: #2413 (#5178, @allenxu404) * Reduce CRD size. (#5174, @jxun) * Fix restic backups to multiple backup storage locations bug (#5172, @qiuming-best) - * Add changes for Kopia Integration: Unified Repository Provider - Repo Password (#5167, @lyndon) + * Add changes for Kopia Integration: Unified Repository Provider - Repo Password (#5167, @Lyndon-Li) * Skip registering "crd-remap-version" plugin when feature flag "EnableAPIGroupVersions" is set (#5165, @reasonerjt) * Kopia uploader integration on shim progress uploader module (#5163, @qiuming-best) * Add labeled and unlabeled events for PR changelog check action. (#5157, @jxun) * VolumeSnapshotLocation refactor with kubebuilder. (#5148, @jxun) * Delay CA file deletion in PVB controller. (#5145, @jxun) * This commit splits the pkg/restic package into several packages to support Kopia integration works (#5143, @ywk253100) - * Kopia Integration: Add the Unified Repository Interface definition. -Kopia Integration: Add the changes for Unified Repository storage config. - -Related Issues; #5076, #5080 (#5142, @lyndon) + * Kopia Integration: Add the Unified Repository Interface definition. Kopia Integration: Add the changes for Unified Repository storage config. Related Issues; #5076, #5080 (#5142, @Lyndon-Li) * Update the CRD for kopia integration (#5135, @reasonerjt) * Let "make shell xxx" respect GOPROXY (#5128, @reasonerjt) * Modify BackupStoreGetter to avoid BSL spec changes (#5122, @sseago) @@ -189,5 +258,5 @@ Related Issues; #5076, #5080 (#5142, @lyndon) * When spec.RestoreStatus is empty, don't restore status (#5008, @sseago) * Added DownloadTargetKindCSIBackupVolumeSnapshots for retrieving the signed URL to download only the ``-csi-volumesnapshots.json.gz and DownloadTargetKindCSIBackupVolumeSnapshotContents to download only ``-csi-volumesnapshotcontents.json.gz in the DownloadRequest CR structure. These files are already present in the backup layout. (#4980, @anshulahuja98) * Refactor BackupItemAction proto and related code to backupitemaction/v1 package. This is part of implementation of the plugin version design https://github.com/vmware-tanzu/velero/blob/main/design/plugin-versioning.md (#4943, @phuongatemc) - * Unified Repository Design (#4926, @lyndon) + * Unified Repository Design (#4926, @Lyndon-Li) * Add credentials to volume snapshot locations (#4864, @sseago) \ No newline at end of file diff --git a/changelogs/CHANGELOG-1.8.md b/changelogs/CHANGELOG-1.8.md index 1e250f65d7..7c8f019467 100644 --- a/changelogs/CHANGELOG-1.8.md +++ b/changelogs/CHANGELOG-1.8.md @@ -103,7 +103,7 @@ Also added DownloadTargetKindBackupItemSnapshots for retrieving the signed URL t * Fix CVE-2020-29652 and CVE-2020-26160 (#4274, @ywk253100) * Refine tag-release.sh to align with change in release process (#4185, @reasonerjt) * Fix plugins incompatible issue in upgrade test (#4141, @danfengliu) -* Verify group before treating resource as cohabitating (#4126, @sseago) +* Verify group before treating resource as cohabiting (#4126, @sseago) * Added ItemSnapshotter plugin definition and plugin framework - addresses #3533. Part of the Upload Progress enhancement (#3533) (#4077, @dsmithuchida) * Add upgrade test in E2E test (#4058, @danfengliu) diff --git a/changelogs/unreleased/4864-sseago b/changelogs/unreleased/4864-sseago deleted file mode 100644 index fdbe784915..0000000000 --- a/changelogs/unreleased/4864-sseago +++ /dev/null @@ -1 +0,0 @@ -Add credentials to volume snapshot locations diff --git a/changelogs/unreleased/4926-lyndon b/changelogs/unreleased/4926-lyndon deleted file mode 100644 index d5c23db47c..0000000000 --- a/changelogs/unreleased/4926-lyndon +++ /dev/null @@ -1 +0,0 @@ -Unified Repository Design \ No newline at end of file diff --git a/changelogs/unreleased/4943-phuongatemc b/changelogs/unreleased/4943-phuongatemc deleted file mode 100644 index 6443fc603f..0000000000 --- a/changelogs/unreleased/4943-phuongatemc +++ /dev/null @@ -1 +0,0 @@ -Refactor BackupItemAction proto and related code to backupitemaction/v1 package. This is part of implementation of the plugin version design https://github.com/vmware-tanzu/velero/blob/main/design/plugin-versioning.md diff --git a/changelogs/unreleased/4980-anshulahuja98 b/changelogs/unreleased/4980-anshulahuja98 deleted file mode 100644 index 8332b5f2cf..0000000000 --- a/changelogs/unreleased/4980-anshulahuja98 +++ /dev/null @@ -1 +0,0 @@ -Added DownloadTargetKindCSIBackupVolumeSnapshots for retrieving the signed URL to download only the ``-csi-volumesnapshots.json.gz and DownloadTargetKindCSIBackupVolumeSnapshotContents to download only ``-csi-volumesnapshotcontents.json.gz in the DownloadRequest CR structure. These files are already present in the backup layout. \ No newline at end of file diff --git a/changelogs/unreleased/5008-sseago b/changelogs/unreleased/5008-sseago deleted file mode 100644 index 69e4039326..0000000000 --- a/changelogs/unreleased/5008-sseago +++ /dev/null @@ -1 +0,0 @@ -When spec.RestoreStatus is empty, don't restore status diff --git a/changelogs/unreleased/5041-jxun b/changelogs/unreleased/5041-jxun deleted file mode 100644 index eeb5524f74..0000000000 --- a/changelogs/unreleased/5041-jxun +++ /dev/null @@ -1 +0,0 @@ -Delete opened issues triage action. \ No newline at end of file diff --git a/changelogs/unreleased/5051-niulechuan b/changelogs/unreleased/5051-niulechuan deleted file mode 100644 index 1cba2e2480..0000000000 --- a/changelogs/unreleased/5051-niulechuan +++ /dev/null @@ -1 +0,0 @@ -Fix typo in doc, in https://velero.io/docs/main/restore-reference/ "Restore order" section, "Mamespace" should be "Namespace". diff --git a/changelogs/unreleased/5052-jxun b/changelogs/unreleased/5052-jxun deleted file mode 100644 index 8df837959b..0000000000 --- a/changelogs/unreleased/5052-jxun +++ /dev/null @@ -1 +0,0 @@ -Modify Github actions. \ No newline at end of file diff --git a/changelogs/unreleased/5053-niulechuan b/changelogs/unreleased/5053-niulechuan deleted file mode 100644 index f44c46a699..0000000000 --- a/changelogs/unreleased/5053-niulechuan +++ /dev/null @@ -1 +0,0 @@ -Move 'velero.io/exclude-from-backup' label string to const diff --git a/changelogs/unreleased/5064-jxun b/changelogs/unreleased/5064-jxun deleted file mode 100644 index 4de8a84605..0000000000 --- a/changelogs/unreleased/5064-jxun +++ /dev/null @@ -1 +0,0 @@ -Exclude "csinodes.storage.k8s.io" and "volumeattachments.storage.k8s.io" from restore by default. \ No newline at end of file diff --git a/changelogs/unreleased/5101-ywk253100 b/changelogs/unreleased/5101-ywk253100 deleted file mode 100644 index ade00f2a99..0000000000 --- a/changelogs/unreleased/5101-ywk253100 +++ /dev/null @@ -1 +0,0 @@ - Fix bsl validation bug: the BSL is validated continually and doesn't respect the validation period configured \ No newline at end of file diff --git a/changelogs/unreleased/5104-jxun b/changelogs/unreleased/5104-jxun deleted file mode 100644 index 56c6c4f1e9..0000000000 --- a/changelogs/unreleased/5104-jxun +++ /dev/null @@ -1 +0,0 @@ -Make CSI snapshot creation timeout configurable. \ No newline at end of file diff --git a/changelogs/unreleased/5110-reasonerjt b/changelogs/unreleased/5110-reasonerjt deleted file mode 100644 index 350f91fa17..0000000000 --- a/changelogs/unreleased/5110-reasonerjt +++ /dev/null @@ -1 +0,0 @@ -Dump stack trace when the plugin server handles panic \ No newline at end of file diff --git a/changelogs/unreleased/5122-sseago b/changelogs/unreleased/5122-sseago deleted file mode 100644 index ec8dc473ef..0000000000 --- a/changelogs/unreleased/5122-sseago +++ /dev/null @@ -1 +0,0 @@ -Modify BackupStoreGetter to avoid BSL spec changes diff --git a/changelogs/unreleased/5128-reasonerjt b/changelogs/unreleased/5128-reasonerjt deleted file mode 100644 index 3ba53b0590..0000000000 --- a/changelogs/unreleased/5128-reasonerjt +++ /dev/null @@ -1 +0,0 @@ -Let "make shell xxx" respect GOPROXY \ No newline at end of file diff --git a/changelogs/unreleased/5135-reasonerjt b/changelogs/unreleased/5135-reasonerjt deleted file mode 100644 index 0505ab0468..0000000000 --- a/changelogs/unreleased/5135-reasonerjt +++ /dev/null @@ -1 +0,0 @@ -Update the CRD for kopia integration \ No newline at end of file diff --git a/changelogs/unreleased/5142-lyndon b/changelogs/unreleased/5142-lyndon deleted file mode 100644 index 10286cf0bc..0000000000 --- a/changelogs/unreleased/5142-lyndon +++ /dev/null @@ -1,4 +0,0 @@ -Kopia Integration: Add the Unified Repository Interface definition. -Kopia Integration: Add the changes for Unified Repository storage config. - -Related Issues; #5076, #5080 \ No newline at end of file diff --git a/changelogs/unreleased/5143-ywk253100 b/changelogs/unreleased/5143-ywk253100 deleted file mode 100644 index cf52136456..0000000000 --- a/changelogs/unreleased/5143-ywk253100 +++ /dev/null @@ -1 +0,0 @@ -This commit splits the pkg/restic package into several packages to support Kopia integration works \ No newline at end of file diff --git a/changelogs/unreleased/5145-jxun b/changelogs/unreleased/5145-jxun deleted file mode 100644 index 959ecf73df..0000000000 --- a/changelogs/unreleased/5145-jxun +++ /dev/null @@ -1 +0,0 @@ -Delay CA file deletion in PVB controller. \ No newline at end of file diff --git a/changelogs/unreleased/5148-jxun b/changelogs/unreleased/5148-jxun deleted file mode 100644 index e0e489b8dc..0000000000 --- a/changelogs/unreleased/5148-jxun +++ /dev/null @@ -1 +0,0 @@ -VolumeSnapshotLocation refactor with kubebuilder. \ No newline at end of file diff --git a/changelogs/unreleased/5157-jxun b/changelogs/unreleased/5157-jxun deleted file mode 100644 index e42a0c93c3..0000000000 --- a/changelogs/unreleased/5157-jxun +++ /dev/null @@ -1 +0,0 @@ -Add labeled and unlabeled events for PR changelog check action. \ No newline at end of file diff --git a/changelogs/unreleased/5163-qiuming-best b/changelogs/unreleased/5163-qiuming-best deleted file mode 100644 index c9669c7e74..0000000000 --- a/changelogs/unreleased/5163-qiuming-best +++ /dev/null @@ -1 +0,0 @@ -Kopia uploader integration on shim progress uploader module diff --git a/changelogs/unreleased/5165-reasonerjt b/changelogs/unreleased/5165-reasonerjt deleted file mode 100644 index c33f179b3f..0000000000 --- a/changelogs/unreleased/5165-reasonerjt +++ /dev/null @@ -1 +0,0 @@ -Skip registering "crd-remap-version" plugin when feature flag "EnableAPIGroupVersions" is set \ No newline at end of file diff --git a/changelogs/unreleased/5167-lyndon b/changelogs/unreleased/5167-lyndon deleted file mode 100644 index 7bef82dccb..0000000000 --- a/changelogs/unreleased/5167-lyndon +++ /dev/null @@ -1 +0,0 @@ -Add changes for Kopia Integration: Unified Repository Provider - Repo Password \ No newline at end of file diff --git a/changelogs/unreleased/5172-qiuming-best b/changelogs/unreleased/5172-qiuming-best deleted file mode 100644 index fa57d3d30e..0000000000 --- a/changelogs/unreleased/5172-qiuming-best +++ /dev/null @@ -1 +0,0 @@ -Fix restic backups to multiple backup storage locations bug diff --git a/changelogs/unreleased/5174-jxun b/changelogs/unreleased/5174-jxun deleted file mode 100644 index 4c3991b28f..0000000000 --- a/changelogs/unreleased/5174-jxun +++ /dev/null @@ -1 +0,0 @@ -Reduce CRD size. \ No newline at end of file diff --git a/changelogs/unreleased/5178-allenxu404 b/changelogs/unreleased/5178-allenxu404 deleted file mode 100644 index 2c1b4b0e04..0000000000 --- a/changelogs/unreleased/5178-allenxu404 +++ /dev/null @@ -1,2 +0,0 @@ -Treat namespaces with exclude label as excludedNamespaces -Related issue: #2413 diff --git a/changelogs/unreleased/5179-lyndon b/changelogs/unreleased/5179-lyndon deleted file mode 100644 index 65ae6cc00c..0000000000 --- a/changelogs/unreleased/5179-lyndon +++ /dev/null @@ -1 +0,0 @@ -Add changes for Kopia Integration: Unified Repository Provider - method implementation \ No newline at end of file diff --git a/changelogs/unreleased/5181-jxun b/changelogs/unreleased/5181-jxun deleted file mode 100644 index 4333691d26..0000000000 --- a/changelogs/unreleased/5181-jxun +++ /dev/null @@ -1 +0,0 @@ -Add annotation "pv.kubernetes.io/migrated-to" for CSI checking. \ No newline at end of file diff --git a/changelogs/unreleased/5212-reasonerjt b/changelogs/unreleased/5212-reasonerjt deleted file mode 100644 index be58b9adb7..0000000000 --- a/changelogs/unreleased/5212-reasonerjt +++ /dev/null @@ -1 +0,0 @@ -Add parameter "uploader-type" to velero server \ No newline at end of file diff --git a/changelogs/unreleased/5214-qiuming-best b/changelogs/unreleased/5214-qiuming-best deleted file mode 100644 index 9c2ddd1de7..0000000000 --- a/changelogs/unreleased/5214-qiuming-best +++ /dev/null @@ -1 +0,0 @@ -Uploader Implementation: Restic backup and restore diff --git a/changelogs/unreleased/5215-allenxu404 b/changelogs/unreleased/5215-allenxu404 deleted file mode 100644 index 48275bb251..0000000000 --- a/changelogs/unreleased/5215-allenxu404 +++ /dev/null @@ -1 +0,0 @@ -Refactor GCController with kubebuilder \ No newline at end of file diff --git a/changelogs/unreleased/5217-lilongfeng0902 b/changelogs/unreleased/5217-lilongfeng0902 deleted file mode 100644 index 819069ac3e..0000000000 --- a/changelogs/unreleased/5217-lilongfeng0902 +++ /dev/null @@ -1 +0,0 @@ -check vsc null pointer \ No newline at end of file diff --git a/changelogs/unreleased/5218-jxun b/changelogs/unreleased/5218-jxun deleted file mode 100644 index d2274476e0..0000000000 --- a/changelogs/unreleased/5218-jxun +++ /dev/null @@ -1 +0,0 @@ -Migrate backup sync controller from code-generator to kubebuilder. \ No newline at end of file diff --git a/changelogs/unreleased/5221-qiuming-best b/changelogs/unreleased/5221-qiuming-best deleted file mode 100644 index 2de71e91ab..0000000000 --- a/changelogs/unreleased/5221-qiuming-best +++ /dev/null @@ -1 +0,0 @@ -Uploader Implementation: Kopia backup and restore diff --git a/changelogs/unreleased/5231-lyndon b/changelogs/unreleased/5231-lyndon deleted file mode 100644 index c6faa07e34..0000000000 --- a/changelogs/unreleased/5231-lyndon +++ /dev/null @@ -1 +0,0 @@ -Add changes for Kopia Integration: Kopia Lib - initialize Kopia repo \ No newline at end of file diff --git a/changelogs/unreleased/5233-lyndon b/changelogs/unreleased/5233-lyndon deleted file mode 100644 index 498111d471..0000000000 --- a/changelogs/unreleased/5233-lyndon +++ /dev/null @@ -1,2 +0,0 @@ -Add changes for Kopia Integration: Kopia Lib - method implementation -Add changes to write Kopia Repository logs to Velero log \ No newline at end of file diff --git a/changelogs/unreleased/5234-reasonerjt b/changelogs/unreleased/5234-reasonerjt deleted file mode 100644 index 34fd5768ad..0000000000 --- a/changelogs/unreleased/5234-reasonerjt +++ /dev/null @@ -1 +0,0 @@ -Remove reference to non-existent doc \ No newline at end of file diff --git a/changelogs/unreleased/5236-sseago b/changelogs/unreleased/5236-sseago deleted file mode 100644 index 4d295cce85..0000000000 --- a/changelogs/unreleased/5236-sseago +++ /dev/null @@ -1 +0,0 @@ -Check for empty ns list before checking nslist[0] diff --git a/changelogs/unreleased/5239-shubham-pampattiwar b/changelogs/unreleased/5239-shubham-pampattiwar deleted file mode 100644 index 693e1c4fb3..0000000000 --- a/changelogs/unreleased/5239-shubham-pampattiwar +++ /dev/null @@ -1 +0,0 @@ -Fix edge cases for already exists resources \ No newline at end of file diff --git a/changelogs/unreleased/5241-jxun b/changelogs/unreleased/5241-jxun deleted file mode 100644 index 23e5c0897d..0000000000 --- a/changelogs/unreleased/5241-jxun +++ /dev/null @@ -1 +0,0 @@ -Controller refactor code modifications. \ No newline at end of file diff --git a/changelogs/unreleased/5247-divolgin b/changelogs/unreleased/5247-divolgin deleted file mode 100644 index 7a6e24cfec..0000000000 --- a/changelogs/unreleased/5247-divolgin +++ /dev/null @@ -1 +0,0 @@ -Fix nil pointer panic when restoring StatefulSets \ No newline at end of file diff --git a/changelogs/unreleased/5248-allenxu404 b/changelogs/unreleased/5248-allenxu404 deleted file mode 100644 index 8551bc8cfa..0000000000 --- a/changelogs/unreleased/5248-allenxu404 +++ /dev/null @@ -1 +0,0 @@ -equip gc controller with configurable frequency \ No newline at end of file diff --git a/changelogs/unreleased/5252-cleverhu b/changelogs/unreleased/5252-cleverhu deleted file mode 100644 index d478a57505..0000000000 --- a/changelogs/unreleased/5252-cleverhu +++ /dev/null @@ -1 +0,0 @@ -Add csiSnapshotTimeout for describe backup \ No newline at end of file diff --git a/changelogs/unreleased/5259-lyndon b/changelogs/unreleased/5259-lyndon deleted file mode 100644 index c56e2a3ef9..0000000000 --- a/changelogs/unreleased/5259-lyndon +++ /dev/null @@ -1 +0,0 @@ -Fill gaps for Kopia path of PVBR: integrate Repo Manager with Unified Repo; pass UploaderType to PVBR backupper and restorer; pass RepositoryType to BackupRepository controller and Repo Ensurer \ No newline at end of file diff --git a/changelogs/unreleased/5263-sseago b/changelogs/unreleased/5263-sseago deleted file mode 100644 index 384f5f9184..0000000000 --- a/changelogs/unreleased/5263-sseago +++ /dev/null @@ -1 +0,0 @@ -Don't move velero v1 plugins to new proto dir diff --git a/changelogs/unreleased/5271-sseago b/changelogs/unreleased/5271-sseago deleted file mode 100644 index 339b81b696..0000000000 --- a/changelogs/unreleased/5271-sseago +++ /dev/null @@ -1 +0,0 @@ - plugin/clientmgmt refactoring for BackupItemAction v1 diff --git a/changelogs/unreleased/5279-ywk253100 b/changelogs/unreleased/5279-ywk253100 deleted file mode 100644 index 51dd99deaf..0000000000 --- a/changelogs/unreleased/5279-ywk253100 +++ /dev/null @@ -1 +0,0 @@ -Support pause/unpause schedules \ No newline at end of file diff --git a/changelogs/unreleased/5282-lyndon b/changelogs/unreleased/5282-lyndon deleted file mode 100644 index ef34f89939..0000000000 --- a/changelogs/unreleased/5282-lyndon +++ /dev/null @@ -1 +0,0 @@ -Add changes for problems/enhancements found during smoking test for Kopia pod volume backup/restore \ No newline at end of file diff --git a/changelogs/unreleased/5283-blackpiglet b/changelogs/unreleased/5283-blackpiglet deleted file mode 100644 index 1d08df3e73..0000000000 --- a/changelogs/unreleased/5283-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -Add backup status checking in schedule controller. \ No newline at end of file diff --git a/changelogs/unreleased/5291-cleverhu b/changelogs/unreleased/5291-cleverhu deleted file mode 100644 index 0cf9b7a157..0000000000 --- a/changelogs/unreleased/5291-cleverhu +++ /dev/null @@ -1 +0,0 @@ -Add more detailed comments for labels columns. \ No newline at end of file diff --git a/changelogs/unreleased/5293-cleverhu b/changelogs/unreleased/5293-cleverhu deleted file mode 100644 index 8c1c03d754..0000000000 --- a/changelogs/unreleased/5293-cleverhu +++ /dev/null @@ -1 +0,0 @@ -Optimize code for restore exists resources. \ No newline at end of file diff --git a/changelogs/unreleased/5294-cleverhu b/changelogs/unreleased/5294-cleverhu deleted file mode 100644 index b89daf7636..0000000000 --- a/changelogs/unreleased/5294-cleverhu +++ /dev/null @@ -1 +0,0 @@ -change CSISnapshotTimeout from pointer to normal variables. \ No newline at end of file diff --git a/changelogs/unreleased/5297-blackpiglet b/changelogs/unreleased/5297-blackpiglet deleted file mode 100644 index 8e46fb5d0a..0000000000 --- a/changelogs/unreleased/5297-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -Remove github.com/apex/log logger. \ No newline at end of file diff --git a/changelogs/unreleased/5299-jxun b/changelogs/unreleased/5299-jxun deleted file mode 100644 index ce574df7a7..0000000000 --- a/changelogs/unreleased/5299-jxun +++ /dev/null @@ -1 +0,0 @@ -Remove snapshot related lister, informer and client from backup controller. \ No newline at end of file diff --git a/changelogs/unreleased/5308-lyndon b/changelogs/unreleased/5308-lyndon deleted file mode 100644 index 5e9c4a544c..0000000000 --- a/changelogs/unreleased/5308-lyndon +++ /dev/null @@ -1 +0,0 @@ -Refactor the repoEnsurer code to use controller runtime client and wrap some common BackupRepository operations to share with other modules \ No newline at end of file diff --git a/changelogs/unreleased/5312-sseago b/changelogs/unreleased/5312-sseago deleted file mode 100644 index 2856fc123f..0000000000 --- a/changelogs/unreleased/5312-sseago +++ /dev/null @@ -1 +0,0 @@ -RestoreItemAction v1 refactoring for plugin api versioning diff --git a/changelogs/unreleased/5314-allenxu404 b/changelogs/unreleased/5314-allenxu404 deleted file mode 100644 index 9125ad1310..0000000000 --- a/changelogs/unreleased/5314-allenxu404 +++ /dev/null @@ -1 +0,0 @@ -Change the status of restore to completed from partially failed when restore empty backup \ No newline at end of file diff --git a/changelogs/unreleased/5318-sseago b/changelogs/unreleased/5318-sseago deleted file mode 100644 index edb2f09acf..0000000000 --- a/changelogs/unreleased/5318-sseago +++ /dev/null @@ -1 +0,0 @@ -plugin versioning v1 refactor for VolumeSnapshotter diff --git a/changelogs/unreleased/5319-lyndon b/changelogs/unreleased/5319-lyndon deleted file mode 100644 index 5c87431c56..0000000000 --- a/changelogs/unreleased/5319-lyndon +++ /dev/null @@ -1 +0,0 @@ -Fix issue 4874 and 4752: check the daemonset pod is running in the node where the workload pod resides before running the PVB for the pod \ No newline at end of file diff --git a/changelogs/unreleased/5322-qiuming-best b/changelogs/unreleased/5322-qiuming-best deleted file mode 100644 index b44ed0b5f2..0000000000 --- a/changelogs/unreleased/5322-qiuming-best +++ /dev/null @@ -1 +0,0 @@ -Fix PVB finds wrong parent snapshot diff --git a/changelogs/unreleased/5329-kaovilai b/changelogs/unreleased/5329-kaovilai deleted file mode 100644 index 81615413cd..0000000000 --- a/changelogs/unreleased/5329-kaovilai +++ /dev/null @@ -1 +0,0 @@ -Cancel downloadRequest when timeout without downloadURL \ No newline at end of file diff --git a/changelogs/unreleased/5331-danfengliu b/changelogs/unreleased/5331-danfengliu deleted file mode 100644 index a48cc7bec3..0000000000 --- a/changelogs/unreleased/5331-danfengliu +++ /dev/null @@ -1 +0,0 @@ -Add opt-in and opt-out PersistentVolume backup to E2E tests \ No newline at end of file diff --git a/changelogs/unreleased/5335-shubham-pampattiwar b/changelogs/unreleased/5335-shubham-pampattiwar deleted file mode 100644 index 803f0f3cb5..0000000000 --- a/changelogs/unreleased/5335-shubham-pampattiwar +++ /dev/null @@ -1 +0,0 @@ -Increase ensure restic repository timeout to 5m \ No newline at end of file diff --git a/changelogs/unreleased/5344-kaovilai b/changelogs/unreleased/5344-kaovilai deleted file mode 100644 index 574844a8a1..0000000000 --- a/changelogs/unreleased/5344-kaovilai +++ /dev/null @@ -1 +0,0 @@ -Resolve gopkg.in/yaml.v3 vulnerabilities by upgrading gopkg.in/yaml.v3 to v3.0.1 \ No newline at end of file diff --git a/changelogs/unreleased/5347-qiuming-best b/changelogs/unreleased/5347-qiuming-best deleted file mode 100644 index c9f946e228..0000000000 --- a/changelogs/unreleased/5347-qiuming-best +++ /dev/null @@ -1 +0,0 @@ -Fix restore cmd extraflag overwrite bug diff --git a/changelogs/unreleased/5350-blackpiglet b/changelogs/unreleased/5350-blackpiglet deleted file mode 100644 index c44dc8535f..0000000000 --- a/changelogs/unreleased/5350-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -Clarify the help message for the default value of parameter --snapshot-volumes, when it's not set. \ No newline at end of file diff --git a/changelogs/unreleased/5353-cleverhu b/changelogs/unreleased/5353-cleverhu deleted file mode 100644 index 91e8153f8e..0000000000 --- a/changelogs/unreleased/5353-cleverhu +++ /dev/null @@ -1 +0,0 @@ -Add useOwnerReferencesInBackup field doc for schedule. \ No newline at end of file diff --git a/changelogs/unreleased/5355-danfengliu b/changelogs/unreleased/5355-danfengliu deleted file mode 100644 index e365367f33..0000000000 --- a/changelogs/unreleased/5355-danfengliu +++ /dev/null @@ -1 +0,0 @@ -Add E2E test for schedule backup \ No newline at end of file diff --git a/changelogs/unreleased/5359-lyndon b/changelogs/unreleased/5359-lyndon deleted file mode 100644 index 230ba3f724..0000000000 --- a/changelogs/unreleased/5359-lyndon +++ /dev/null @@ -1 +0,0 @@ -Fix a repoEnsurer problem introduced by the refactor - The repoEnsurer didn't check "" state of BackupRepository, as a result, the function GetBackupRepository always returns without an error even though the ensreReady is specified. \ No newline at end of file diff --git a/changelogs/unreleased/5362-niulechuan b/changelogs/unreleased/5362-niulechuan deleted file mode 100644 index d528bfa8bb..0000000000 --- a/changelogs/unreleased/5362-niulechuan +++ /dev/null @@ -1 +0,0 @@ -Added backupController's UT to test the prepareBackupRequest() method BackupStorageLocation processing logic diff --git a/changelogs/unreleased/5370-lyndon b/changelogs/unreleased/5370-lyndon deleted file mode 100644 index 11351c3174..0000000000 --- a/changelogs/unreleased/5370-lyndon +++ /dev/null @@ -1 +0,0 @@ -Pod Volume Backup/Restore Refactor: Rename parameters in CRDs and commands to remove "Restic" word \ No newline at end of file diff --git a/changelogs/unreleased/5377-qiuming-best b/changelogs/unreleased/5377-qiuming-best deleted file mode 100644 index bd3cf12368..0000000000 --- a/changelogs/unreleased/5377-qiuming-best +++ /dev/null @@ -1 +0,0 @@ -Fix restore error with flag namespace-mappings diff --git a/changelogs/unreleased/5387-lyndon b/changelogs/unreleased/5387-lyndon deleted file mode 100644 index 63cf32bf37..0000000000 --- a/changelogs/unreleased/5387-lyndon +++ /dev/null @@ -1 +0,0 @@ -Fix issue 5386: Velero providers a full URL as the S3Url while the underlying minio client only accept the host part of the URL as the endpoint and the schema should be specified separately. \ No newline at end of file diff --git a/changelogs/unreleased/5388-blackpiglet b/changelogs/unreleased/5388-blackpiglet deleted file mode 100644 index 2bd9f73752..0000000000 --- a/changelogs/unreleased/5388-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -Add some corner cases checking for CSI snapshot in backup controller. \ No newline at end of file diff --git a/changelogs/unreleased/5390-lyndon b/changelogs/unreleased/5390-lyndon deleted file mode 100644 index 091b074c7b..0000000000 --- a/changelogs/unreleased/5390-lyndon +++ /dev/null @@ -1 +0,0 @@ -Rename Velero daemonset from "restic" to "node-agent" \ No newline at end of file diff --git a/changelogs/unreleased/5394-blackpiglet b/changelogs/unreleased/5394-blackpiglet deleted file mode 100644 index 20d2cbcede..0000000000 --- a/changelogs/unreleased/5394-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -Fix Test_prepareBackupRequest_BackupStorageLocation UT failure. \ No newline at end of file diff --git a/changelogs/unreleased/5396-blackpiglet b/changelogs/unreleased/5396-blackpiglet deleted file mode 100644 index b4808596e7..0000000000 --- a/changelogs/unreleased/5396-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -update velero using klog to version v2.9.0 \ No newline at end of file diff --git a/changelogs/unreleased/5401-shubham-pampattiwar b/changelogs/unreleased/5401-shubham-pampattiwar deleted file mode 100644 index e42b5260c5..0000000000 --- a/changelogs/unreleased/5401-shubham-pampattiwar +++ /dev/null @@ -1 +0,0 @@ -Add nil check before execution of csi snapshot delete \ No newline at end of file diff --git a/changelogs/unreleased/5412-allenxu404 b/changelogs/unreleased/5412-allenxu404 deleted file mode 100644 index 3258f8c147..0000000000 --- a/changelogs/unreleased/5412-allenxu404 +++ /dev/null @@ -1 +0,0 @@ -Change B/R describe CLI to support Kopia \ No newline at end of file diff --git a/changelogs/unreleased/5429-reasonerjt b/changelogs/unreleased/5429-reasonerjt deleted file mode 100644 index 2fdd8fe8d8..0000000000 --- a/changelogs/unreleased/5429-reasonerjt +++ /dev/null @@ -1 +0,0 @@ -Skip the exclusion check for additional resources returned by BIA \ No newline at end of file diff --git a/changelogs/unreleased/5432-lyndon b/changelogs/unreleased/5432-lyndon deleted file mode 100644 index f6eb6708e7..0000000000 --- a/changelogs/unreleased/5432-lyndon +++ /dev/null @@ -1 +0,0 @@ -Rename Velero pod volume restore init helper from "velero-restic-restore-helper" to "velero-restore-helper" \ No newline at end of file diff --git a/changelogs/unreleased/5441-sseago b/changelogs/unreleased/5441-sseago deleted file mode 100644 index fd8f7b7d21..0000000000 --- a/changelogs/unreleased/5441-sseago +++ /dev/null @@ -1 +0,0 @@ -moved RIA execute input/output structs back to velero package diff --git a/changelogs/unreleased/5444-lyndon b/changelogs/unreleased/5444-lyndon deleted file mode 100644 index ef6466bf04..0000000000 --- a/changelogs/unreleased/5444-lyndon +++ /dev/null @@ -1 +0,0 @@ -Remove irrational "Restic" names in Velero code after the PVBR refactor \ No newline at end of file diff --git a/changelogs/unreleased/5446-allenxu404 b/changelogs/unreleased/5446-allenxu404 deleted file mode 100644 index 6d7675deed..0000000000 --- a/changelogs/unreleased/5446-allenxu404 +++ /dev/null @@ -1 +0,0 @@ -Change subcommand `velero restic repo` to `velero repo` \ No newline at end of file diff --git a/changelogs/unreleased/5449-blackpiglet b/changelogs/unreleased/5449-blackpiglet deleted file mode 100644 index 3ab9934eee..0000000000 --- a/changelogs/unreleased/5449-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -Add VolumeSnapshot client back. \ No newline at end of file diff --git a/changelogs/unreleased/5459-lyndon b/changelogs/unreleased/5459-lyndon deleted file mode 100644 index ba04f2082a..0000000000 --- a/changelogs/unreleased/5459-lyndon +++ /dev/null @@ -1 +0,0 @@ -Upgrade velero docker image to use go 1.18 and upgrade golangci-lint to 1.45.0 \ No newline at end of file diff --git a/changelogs/unreleased/5468-qiuming-best b/changelogs/unreleased/5468-qiuming-best deleted file mode 100644 index 96b49b4df6..0000000000 --- a/changelogs/unreleased/5468-qiuming-best +++ /dev/null @@ -1 +0,0 @@ -Add v1.10 velero upgrade doc diff --git a/changelogs/unreleased/5471-kcboyle b/changelogs/unreleased/5471-kcboyle deleted file mode 100644 index 341cef245f..0000000000 --- a/changelogs/unreleased/5471-kcboyle +++ /dev/null @@ -1,6 +0,0 @@ -Update the k8s.io dependencies to 0.24.0. -This also required an update to github.com/bombsimon/logrusr/v3. -Removed the `WithClusterName` method -as it is a "legacy field that was -always cleared by the system and never used" as per upstream k8s -https://github.com/kubernetes/apimachinery/blob/release-1.24/pkg/apis/meta/v1/types.go#L257-L259 \ No newline at end of file diff --git a/changelogs/unreleased/5478-lyndon b/changelogs/unreleased/5478-lyndon deleted file mode 100644 index d068694491..0000000000 --- a/changelogs/unreleased/5478-lyndon +++ /dev/null @@ -1 +0,0 @@ -Issue fix 5477: create the common way to support S3 compatible object storages that work for both Restic and Kopia; Keep the resticRepoPrefix parameter for compatibility \ No newline at end of file diff --git a/changelogs/unreleased/5483-blackpiglet b/changelogs/unreleased/5483-blackpiglet deleted file mode 100644 index 59d6accf3b..0000000000 --- a/changelogs/unreleased/5483-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -Remove redundancy code block left by #5388. \ No newline at end of file diff --git a/changelogs/unreleased/5484-lyndon b/changelogs/unreleased/5484-lyndon deleted file mode 100644 index 6bd2dced1f..0000000000 --- a/changelogs/unreleased/5484-lyndon +++ /dev/null @@ -1 +0,0 @@ -Refactor Pod Volume Backup/Restore doc to match the new behavior diff --git a/changelogs/unreleased/5499-lyndon b/changelogs/unreleased/5499-lyndon deleted file mode 100644 index 613f6aaeea..0000000000 --- a/changelogs/unreleased/5499-lyndon +++ /dev/null @@ -1 +0,0 @@ -After Pod Volume Backup/Restore refactor, remove all the unreasonable appearance of "restic" word from documents diff --git a/changelogs/unreleased/5512-lyndon b/changelogs/unreleased/5512-lyndon deleted file mode 100644 index 2712304b38..0000000000 --- a/changelogs/unreleased/5512-lyndon +++ /dev/null @@ -1 +0,0 @@ -Fix issue 5505: the pod volume backups/restores except the first one fail under the kopia path if "AZURE_CLOUD_NAME" is specified diff --git a/changelogs/unreleased/5521-blackpiglet b/changelogs/unreleased/5521-blackpiglet deleted file mode 100644 index 93c0b59412..0000000000 --- a/changelogs/unreleased/5521-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -Add credential store in backup deletion controller to support VSL credential. \ No newline at end of file diff --git a/changelogs/unreleased/5526-qiuming-best b/changelogs/unreleased/5526-qiuming-best deleted file mode 100644 index 772f6d4d90..0000000000 --- a/changelogs/unreleased/5526-qiuming-best +++ /dev/null @@ -1 +0,0 @@ -fix restic backup failure with self-signed certification backend storage diff --git a/changelogs/unreleased/5534-qiuming-best b/changelogs/unreleased/5534-qiuming-best deleted file mode 100644 index 09faeac0c2..0000000000 --- a/changelogs/unreleased/5534-qiuming-best +++ /dev/null @@ -1 +0,0 @@ -fix restic backup progress error diff --git a/changelogs/unreleased/5535-ywk253100 b/changelogs/unreleased/5535-ywk253100 deleted file mode 100644 index 186672ea0c..0000000000 --- a/changelogs/unreleased/5535-ywk253100 +++ /dev/null @@ -1 +0,0 @@ -Enhance the restore priorities list to support specifying the low prioritized resources that need to be restored in the last \ No newline at end of file diff --git a/changelogs/unreleased/5572-qiuming-best b/changelogs/unreleased/5572-qiuming-best deleted file mode 100644 index 40d53fc966..0000000000 --- a/changelogs/unreleased/5572-qiuming-best +++ /dev/null @@ -1 +0,0 @@ -Fix controller problematic log output diff --git a/changelogs/unreleased/5574-qiuming-best b/changelogs/unreleased/5574-qiuming-best deleted file mode 100644 index c872212b5c..0000000000 --- a/changelogs/unreleased/5574-qiuming-best +++ /dev/null @@ -1 +0,0 @@ -Add compile restic binary for CVE fix diff --git a/changelogs/unreleased/6317-ywk253100 b/changelogs/unreleased/6317-ywk253100 new file mode 100644 index 0000000000..d96d6c1fc3 --- /dev/null +++ b/changelogs/unreleased/6317-ywk253100 @@ -0,0 +1 @@ +Restore Endpoints before Services \ No newline at end of file diff --git a/changelogs/unreleased/6527-Lyndon-Li b/changelogs/unreleased/6527-Lyndon-Li new file mode 100644 index 0000000000..c1fd3f8d96 --- /dev/null +++ b/changelogs/unreleased/6527-Lyndon-Li @@ -0,0 +1 @@ +Fix issue #6519. Restrict the client manager of node-agent server to include only Velero resources from the server's namespace, otherwise, the controllers will try to reconcile CRs from all the installed Velero namespaces. \ No newline at end of file diff --git a/config/crd/v1/bases/velero.io_backups.yaml b/config/crd/v1/bases/velero.io_backups.yaml index e204e1157b..7739407891 100644 --- a/config/crd/v1/bases/velero.io_backups.yaml +++ b/config/crd/v1/bases/velero.io_backups.yaml @@ -386,9 +386,10 @@ spec: additionalProperties: type: string description: OrderedResources specifies the backup order of resources - of specific Kind. The map key is the Kind name and value is a list - of resource names separated by commas. Each resource name has format - "namespace/resourcename". For cluster resources, simply use "resourcename". + of specific Kind. The map key is the resource name and value is + a list of object names separated by commas. Each resource name has + format "namespace/objectname". For cluster resources, simply use + "objectname". nullable: true type: object snapshotVolumes: diff --git a/config/crd/v1/bases/velero.io_schedules.yaml b/config/crd/v1/bases/velero.io_schedules.yaml index cd2010c504..587bebd4fa 100644 --- a/config/crd/v1/bases/velero.io_schedules.yaml +++ b/config/crd/v1/bases/velero.io_schedules.yaml @@ -421,10 +421,10 @@ spec: additionalProperties: type: string description: OrderedResources specifies the backup order of resources - of specific Kind. The map key is the Kind name and value is - a list of resource names separated by commas. Each resource - name has format "namespace/resourcename". For cluster resources, - simply use "resourcename". + of specific Kind. The map key is the resource name and value + is a list of object names separated by commas. Each resource + name has format "namespace/objectname". For cluster resources, + simply use "objectname". nullable: true type: object snapshotVolumes: diff --git a/config/crd/v1/crds/crds.go b/config/crd/v1/crds/crds.go index ad661abd0b..40bd849518 100644 --- a/config/crd/v1/crds/crds.go +++ b/config/crd/v1/crds/crds.go @@ -30,16 +30,16 @@ import ( var rawCRDs = [][]byte{ []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4WAo\xdc6\x13\xbd\xebW\f\xf2\x1dr\xf9\xa4M\xd0C\v\xddR\xb7\x05\x82&\x86a\a\xbe\x14=P\xe4\xec.c\x8ad\xc9\xe1\xa6ۢ\xff\xbd\x18R\xf2j%\xd9\x1b\a\xa8n\"\x87of\xde\xcc\x1bQU]ו\xf0\xfa\x1eC\xd4ζ \xbc\xc6?\t-\xbf\xc5\xe6\xe1\x87\xd8h\xb79\xbc\xad\x1e\xb4U-\\\xa5H\xae\xbf\xc5\xe8R\x90\xf8\x13n\xb5դ\x9d\xadz$\xa1\x04\x89\xb6\x02\x10\xd6:\x12\xbc\x1c\xf9\x15@:K\xc1\x19\x83\xa1ޡm\x1eR\x87]\xd2Fa\xc8\xe0\xa3\xebÛ\xe6\xfb\xe6M\x05 \x03\xe6\xe3\x9ft\x8f\x91D\xef[\xb0ɘ\n\xc0\x8a\x1e[\xe8\x84|H>\xa0wQ\x93\v\x1acs@\x83\xc15\xdaUѣd\xb7\xbb\xe0\x92o\xe1\xb4QN\x0f!\x95t~\xcc@\xb7#\xd01o\x19\x1d\xe9\xd7\xd5\xed\x0f:R6\xf1&\x05a\xd6\x02\xc9\xdbQ\xdb]2\",\f\xd8A\x94\xcec\v\xd7\x1c\x8b\x17\x12U\x050P\x90c\xabA(\x95I\x15\xe6&hK\x18\xae\x9cI\xfdHf\r\x9f\xa3\xb37\x82\xf6-4#\xed͂\xb2l;\x12\xf6n\x87\xc3;\x1dٹ\x12\x84K0f\xae9\xc5\xfa\xe9\xe8\xf1\f\xe5D\x04L\xf6\nb\xa4\xa0\xed\xae:\x19\x1f\xde\x16*\xe4\x1e{\xd1\x0e\xb6Σ}w\xf3\xfe\xfe\xbb\xbb\xb3e\x00\x1f\x9c\xc7@z,Oy&}9Y\x05P\x18eОr\u05fcf\xc0b\x05\x8a\x1b\x12#\xd0\x1eGNQ\r1\x80\xdb\x02\xedu\x84\x80>`D[Z\xf4\f\x18\xd8HXp\xddg\x94\xd4\xc0\x1d\x06\x86\x81\xb8w\xc9(\xee\xe3\x03\x06\x82\x80\xd2\xed\xac\xfe\xeb\x11;\x02\xb9\xec\xd4\b¡GNO\xae\xa1\x15\x06\x0e\xc2$\xfc?\b\xab\xa0\x17G\b\xc8^ \xd9\t^6\x89\r|t\x01AۭkaO\xe4c\xbb\xd9\xec4\x8dz\x94\xae\xef\x93\xd5t\xdcdi\xe9.\x91\vq\xa3\xf0\x80f\x13\xf5\xae\x16A\xee5\xa1\xa4\x14p#\xbc\xaes\xe86k\xb2\xe9\xd5\xff\u00a0\xe0\xf8\xfa,\xd6E-˓\xc5\xf2L\x05X-\xa0#\x88\xe1h\xc9\xe2D4/1;\xb7?\xdf}\x82\xd1u.Ɯ\xfd\xcc\xfb\xe9`<\x95\x80\t\xd3v\x8b\xa1\x14q\x1b\\\x9f1\xd1*ﴥ\xfc\"\x8dF;\xa7?\xa6\xae\xd7\xc4u\xff#a$\xaeU\x03WyHA\x87\x90<\xabA5\xf0\xde\u0095\xe8\xd1\\\x89\x88\xffy\x01\x98\xe9X3\xb1_W\x82\xe9|\x9d\x1b\x17\xd6&\x1b\xe3\b|\xa2^\xf3\xb1v\xe7Qr\xf9\x98A>\xaa\xb7Zfm\xc0\xd6\x05\x10\v\xfb\xe6\fz]\xba\xfc\x94\xe1wG.\x88\x1d~p\x05sn\xb4\x1a\xdb\xec\xcc\x18\x1cO\x96\"c\\7\\`\x03\xd0^\xd0D\xbf$\xb4}\x1c\x03\xab\xf90\x92-S\bhi\x80\xc97\x88o\x1e\x99FD\x9a\x8c\v\xbe\xcd]\xe8\x80\x0f\xcb\x13c`\f\x06\xc4\v\xd3\xf9\xf2E̿\xba\xb9hk\x93e\xebB/\xa8\\\x17k\x06ZX\xf0\xb5\\t\x06[\xa0\x90\x96\xdb\xcf\xcdQ\x8cQ\xec.e\xf7\xb1X\x95\xcb\xc5p\x04D\xe7\x12=A=\xed\x97Q\xc0\x85r\\\x88\xd4\xefE\xbc\x14\xe7\r۬5\xc4\xec{\xf5\\\bO\xcd\xcck\xfc\xb2\xb2z\x8bB-u\\õ\xa3\xf5\xad'3\\U\xc5b1\xf2=LM\xea\x1c\x8b\x90\xa7+\xa9{\xbcW\xb6\xf0\xf7?\xd5IXBJ\xf4\x84\xeaz\xfe\a6\xcc\xf7\xf1\x87*\xbfJg\xcb\x0fPl\xe1\xb7߫\xe2\n\xd5\xfd\xf8\x93ċ\xff\x06\x00\x00\xff\xff\xc8p\x98۸\x0e\x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xec}Ks\x1c9r\xf0\x9d\xbf\"\x83\xdfA\xbb\x11\xec\xe6L|\a;x\xd3P\x9a0c\xc6\x1a\x86ȕ\x0f\xb6\x0f\xe8\xaa\xecn\fQ@-\x80j\xaa\xed\xf0\u007fw$\x1e\xf5jT\x15\x9a\"wg\x1d\xc2Eb\x15\x90\x002\x13\xf9BV\xf6\xc5j\xb5\xba`5\xff\x82\xdap%o\x80\xd5\x1c\xbfZ\x94\xf4\x97Y?\xfd\xb3Ysu}\xf8\xf1\xe2\x89\xcb\xf2\x06n\x1bcU\xf5\x19\x8djt\x81\x1fp\xcb%\xb7\\ɋ\n-+\x99e7\x17\x00LJe\x19=6\xf4'@\xa1\xa4\xd5J\bԫ\x1d\xca\xf5S\xb3\xc1M\xc3E\x89\xda\x01\x8fS\x1f~X\xff\xd3\xfa\x87\v\x80B\xa3\x1b\xfe\xc8+4\x96U\xf5\r\xc8F\x88\v\x00\xc9*\xbc\x81\r+\x9e\x9aڬ\x0f(P\xab5W\x17\xa6Ƃ\xe6\xdai\xd5\xd47н\xf0C\xc2:\xfc\x1e~r\xa3\xdd\x03\xc1\x8d\xfd\xa5\xf7\xf0Wn\xac{Q\x8bF3\xd1\xce\xe4\x9e\x19.w\x8d`:>\xbd\x000\x85\xaa\xf1\x06>\xd1\x145+\xb0\xbc\x00\b\xdbqS\xae\u0082\x0f?z\b\xc5\x1e+\xe6\xd7\x02\xa0j\x94\xef\xef\xef\xbe\xfc\xff\x87\xc1c\x80\x12M\xa1ym\x1dR\xfc\u0080\x1b`\xf0\xc5m\vt@?\xd8=\xb3\xa0\xb1\xd6hPZ\x03v\x8fP\xb0\xda6\x1aAm\xe1\x97f\x83Z\xa2Eӂ\x06(Dc,j0\x96Y\x04f\x81A\xad\xb8\xb4\xc0%X^!\xfc\xe9\xfd\xfd\x1d\xa8\xcd\xefXX\x03L\x96\xc0\x8cQ\x05g\x16K8(\xd1T\xe8\xc7\xfey\xddB\xad\xb5\xaaQ[\x1e\xf1\xec[\x8f\xabzOG\xdb{G\x18\xf0\xbd\xa0$vB\xbf\x8d\x80E,\x03\xd2h?v\xcfM\xb7]\xc7!\x03\xc0@\x9d\x98\f\x8b_\xc3\x03j\x02\x03f\xaf\x1aQ\x12\x17\x1eP\x13\xc2\n\xb5\x93\xfc\xbfZ\xd8\x06\xacr\x93\nf10@\u05f8\xb4\xa8%\x13p`\xa2\xc1+\x87\x92\x8a\x1dA#\xcd\x02\x8d\xec\xc1s]\xcc\x1a\xfeUi\x04.\xb7\xea\x06\xf6\xd6\xd6\xe6\xe6\xfaz\xc7m\xdd'\xe9[\xb6\x11x\x03V7\xa7\xd3y4l\x94\x12\xc8\xc6Bx\x8c\x87\xcfh,/\x16\xb0p9F\x83\x1f\x95@\x82\x0e/\xdc\xde\x12x\xd8t\xbcf\xd9\x13\x02\x8b\xd8 \xe5 D\x0f\x89\x03\f\xc0\u007fH\xf8@\x92\xab yr\xbaZ\b\x92\x8b\xa3p\xd2R*\x10J\xeeP\xfb\xd9H+\x14\xceK\x80\r\xeeف+\x1d\xb6\xdeI#\xfc\x8aEc1uz\x99\x85\x92o\xb7\xa8\tN\xbdg\x06\x8d7\xfd\xa6\x112\xad\xca\xc0\t\xffIb\x9e\xec\xa3#$q\xaa\xdb\xf9\xd4\xd2I\x1c\x8d\xcfUl\xb4P\xd26\xee\xfc\x96\xfc\xc0ˆ\tw\x94\x99,\xfc~X\xbb\xae\x94H\x98!\xf2ɚ\xbd\xa0\x88+'J\fL\x03%\x11\x94\x86\x8a\xec\xa1Ӯc\v\xaekS\xdb\xde0\x92vʳ\xa8n\x04\x9a0\x95W/\x9d\f\xb8\x9a\x04\xddR\xc4\xdb҂mP\x80A\x81\x85U:\x8d\x8e%\"\xfb\x96#\xd7&\xb0\x98\x90pC\x1b\xa6\xdb\xd8\fHp\x06Ξ\x17{\xaf,\x88\x83\x1c\x1c(\x15\x1aw\xcaY]\x8b\xe3\xd4&a\x89\xf2a\x92\xb9\x83\u07b5\x85#?\x86\x97:\xfc]ː\x8d][\x90\x92C̶\xec\x00V\xcdn\xfb\xff&b\xa3\xd8\u007f\x01\xd3ޝ\f}]\xa6%\x94rrgﶀUm\x8fW\xc0m|\xba\x04\x91L\xadn\xfe\u007f`\u009c\xcf\xf1w㑯\xca\xf1\xb3TY\x82HTi\xa7\xff\a$\x8aS\x16\x0fAWd\x13\xe4\xd7\xfe\xa8+\xe0ۖ \xe5\x15\xf9M\x16\xf5\x882\xdft^^\x03\x199\xfa\x8eZ\xc5l\xb1\xff\xf8\x95,/\xd3\x05\\3\xf12\x1e\xec\xed\xd7h\xcf\x0f\x15\xf3\x02\\p\x01\x1a\xae\xb1\xf2\x81\x9fG\x87\xcd\ue273\xa8\xde\u007f\xfa\x80\xe5\x1cz \x8f\xf3N6\xf2~\xb4\xd8\xfe\xd4\xc1(\xcf\xddF0}Z\xffƇ\xf4\xae\x80\xc1\x13\x1e\xbd\xc5\xc2$\x10q\x18M4\xe1\xe9\x9c\"\xc7\xc5\x16\x1d\x93=\xe1с\t\xc1\xc2\xc5ѹ\xac\xe0\xdb\x13\x1es\xba\x8d\x10Hk\xe2&\x04A\t\x93\xf4\xc0!\xc2Ŗ\xf2\x91\a.\xf0\x1be\xd1\xf2\xe6 _\x90\xc4\x16q\xff\x82m\xb6d\xeb\x05\xcd\x1da\xdf\x19O\":\x05{^gn\x94\xd4\x1c\x18t\xa7%\x86~\xbf0\xc1\xcbv\"\xcf\xf7wr\xda\x1a\x1e\xb6O\xca\xde\xc9+\xf8\xf8\x95\x9b\x10}\xff\xa0\xd0|R\xd6=y\x13t\xfa\x85\xbf\x00\x99~\xa0;^ҋm\xc2C?\x86\x9c\xc1ܾ\xddy\x0f\xaf%\x0f7p'\xc9o\t\xf8p7\x02~\xbay\xfd0lUc\\\x90X*\xb9r\xaar\x9d\x9a\xc9#;\x13\xa4\xd2\x03\x8a\x9c.\xad\x9d\xd4O\x98\t\xf6\x914\x89\x1f\xef\xef8\x04+\xb0\x8c1N\x17\x99g\x16w\xbc\x80\n\xf5nNq\xf4[M\xf2=o\t\x99R\u05f739,O\xb5\xc7\x16Dw2\x047l+:\xb9\x19\xbd\"\xb1\x17\xbbN\x04䧻.\xefȩXg\u007f,b\x97\x95\xa5\xbbKe\xe2\xfe\f\x89\u007f\x06-Nu\xbf_\x98א\x15s!\xd2\xff&5\xe7\x18\xfa\u007f\xa0f\\g\x9c\xe1\xf7\xeejT\xe0`l\x88b\xf5\xa7\xa1\x19\xb8\x01\xa2\uf049ӫ\x9e\xc4\xe6\x14\xc9\x16\x14^\x91\xab\xed\x89\xc5r\x05\xcf{e\xbcNu\xa1\xd9E\x90\xdc\xc0\xe5\x13\x1e/\xafN\xe4\xc0坼\xf4\n\xfelq\xd3Z\vJ\x8a#\\\xba\xb1\x97\xdfb\x04erbV7w\x05\x9dk*\x93/\x19-\x01\x1a\xd8\u07bb\x92\x99;\xb7\xea,>\xac\x95I\xdc&M,\xe5^\x19\xeb#\x8b\x03\xb3\xf4\x9c(\x16x\x1e\n\xd1+`[\u007f\xf3\xadt\xbc\xd3$\xb17\n\xb8\x12\xd5̼\x84%2\xb6\x111\x0f\x94\x1c\xab\xcb\xee\x04{yz\xe9/:\xdd$\xacp\xc6\xc5\"\xdcZ\xab\x02\x8d\x99g\x91\fi\xbd\x10$l\x03\x84\xcc;0\xfe\xc2p>(\x19[\xbeAJH:Ӕ\xff\xf8\xb5\x17\xbd\xa4\xc3O\u007f/1߹\xeb\x02wf\xab\x8a\x8doƳ\x96x\xebG\xc6c\x12\x00y\xd7@\xef\x1aw\xd4\xf3-\xc8\xc0H\u007f\x045]qy\xe7&\x80\x1f_]\xad\xb7B\x12_b\xb8\xdfƱ\x1d\xd2\xdb\a\xee\xf4\xe6ZD\xcaE\xee5\x0e(w\x1a\xe7&C1\x13\xa4T\xb6\x1fN \xb8\xb5*\xdf\x19\xd8rml\u007f\xa1\xb9L\xd1,\x9c\xfe\xae\x9d\xeb9ɏZ\xbf\xc8q\xfa͏\xec\x05\xb2\xf6\xea9\xe6\x17L^Ŧ\x9a\xbb\x14B\xe0[\xe0\x16P\x16\xaa\x91.\xfcBG\xddM\xe1I\xe0\x05t6\xca\xf2\x04\x045\x94M\x95\x87\x80\x95\xe3:.g\xe34\xfd\xee?3.ނlv*\r#\xd5\x06d\x8b\xf9\x18\xfdD\x91\x8a}\xe5US\x01\xab\b\xf5\xb9n\xcf\xd6gq\f(\xde\xe6r8\xb8N\x8dXE\x87\xaa\x16hsO\xa4\xcfڠcbx\x89\xadb\x0e\\\xa0$0\xd82.&.\xcfO\xdbY\xb8=\xc7\xd7\b\xc2\xe2\xf5\x9c\x88\xbc\xc9W\x0e\x15\x19\x81\xd8Lcq^Z\xd7:\xdfT\xbcטg\x9e-\x05\xa5\xa3yVkN\xbc\xa4^\xdbB\v,\xc6\xe4\xf1\xbb\x89vҾ\x9bh\v\xed\xbb\x896پ\x9bh\xcb\xed\xbb\x89\x16\xdaw\x13-\xb6\xef&\xdaw\x13m\xaeۜ\xb4^Z\x91\xff\xe2d\xe2\xe5\xe2*2\xae\xa7\xe7\x968\x03?dS\xdc\xfa\xafOr3,\xefң\x12Y\xc1᳖\x95\xfb\"'\xc5\x01]\xd2E\xa7JڔK: \x91\xbd}\x02\xfdB\x12\xe67d\xdf\xe6$\xfc,\xa5\xf9\f\xf3L\xdb4\x9b\x98h\xaa\xe2$\t<\xc4/{\xc8\xec\xed\xe7\x90\f\xf3u\x9c\x9d\x1bW\xfaw\xcfA\xcdH\xc5YH\xc0\x99O̝\xc3\xd7\xc8\xf5\x18\"L\x0f\x12F\xff0\xf8ZȒ\x99\u038d\t7Ah\xd9\xe1\xc7\xf5\xf0\x8dU!S\x06\x9e\xb9\xdd'\xb6\xf2\xbcG\xe9\xee\xb0䮟\xf6\x1a\xf9-|b5\xc6#(\r\x92\v\x87\xce\x19n\x1d\xa0\x17~\xab\xbd\vw\xf6\xb9\x9cw?\xf2ri^\x9cA3̐\x99\x10\xd1\xe7^\x19\xe5'\n\xe7\xe7\xc8\xcc'\xb5\x9c\x93\x193\xce{\x99\x04\xba\x9c\x0f\x93\xe39.侼 \xe3%3\xdb\xf1\x9b/\xc6rrZ^\x94ɲ\x98\x10\x98\x99\xbf2\xccL\x99\ayF\xd6J\x16r\x963T\xce\xceK\ty \xb3\xfb\xc8\xceFI\xe4\x99\xcc\x02\x9e\xccA\x99\xcb.Y\x88J\x9df\x9e\xe4\xe7\x94̂v\xf9&˙$\xaf\x97/\xfa\x1a6\xf0\xb4\xa8Y\xcc\x06Y\xb4\x91\xe7\u05f7\x98\xefqN\x96\xc7\"\xc6^\x98\xd1\xd1flL\xcc{n\x1e\xc70Oc\x02hN\xf6\xc6Dv\xc6\x04\xc4ٜ\x8dܜ\x8c\t\xd8\vjw\x96Kf^\xa6?\x04\x86E\xfd&\xfeV\x1c\xf5ҍ)=0\x17\x97,\xf4\xdfF݉\x96\xd1j\x9a7?S\x96'\xb7\xfb\xf3\xcdϪ\x11\x96\xd7\u0085\xf3\x0f\xbcL:\x8dv\x8f\xc7\xf6\xb3\xceߕ\xfb\xccist\x90~\xfbܲ\xe7zdD3\x03\xcf(\x04\xb0\x14s\x9d\xec\xbc\xf0߲\x17j\x85$\xf3\xe9\xc0\x85\x0fV\xc3'\xefW\x9e\x83ݗ\\\xa9\x88\xa7\xddcEP◯g\xb8\x1f\xf3\x06\xa2\xb7eݳ\xbf6\xa8\x8f\xa0\x0e\xa8;\x8ba\xe1;\x02\u007f\xd0L#\xbaĭ ?|\x05\x85\x91\xe1\xdc\x1d8x/\xbd\nK\x82\x1d\xad\xd1\xc1\xa13/ZZ\x93x#?`\xa2k:\xf0\xa1\xdaщ\xf7K\xb6gn\x12\xfeۺ\x0e\xe7;\x0f\x8bj\xfbM\x1c\x88\x97\xbb\x103 s\x93\xea\xf3.\xa0\x16\x93\xe8\xdfʕXr&\xb2\xad\xa8\xbc$\xf9\xb7H\x8e?#)\xfe\f\xa7\xe2<\xb7\"\x1bM9\xc9\xefo\xe2\\\xbc\xa1{\xf1\x16\x0e\xc6\xcb\\\x8c\x05\x90\xa3\xa4\xf6\x9ct\xf5\xac\xcb\xd5\xec\xfb\x85\x9c\xcb\xd1\xe5+\x80\xf94\xf4\x8c\xf4\xf3\x8cˁ\xa5\x95f\xa4\x99\x9f\x97^\x9e\x81\xc37r>\xde\xc8\xfdx\v\a\xe4m]\x90E'd\x91sf_\xbf8\xba\xact\x89z6\x18\x9f\xcbj\xb3L6\xf2\x17\x86s\x8e\xbe\xa8\x8d\x15^\xa8\xd7\xc04M\x85\x94ۯ?\v\xf8\x85\xcb\xd2Ӄ\x98\xaa\xa7\xc7]M,\x97\xff\xde\x1a\x15\x9d}\x96\x06:\xbaT0X3튦m\x8e\xfebҬ\xe1#+\xf6Î\xb0g\x06\xb6JWI\x83鲽\x91\xb9\x8e\xa3\xe8\xc9\xe5\x1a\xe0g\xd5^z\xf5+*\x18^\xd5\xe2H~\x00\\\x0e\x87\xbc\x8c\x01\x92\xcccBy\xa6P\xaff\xc1\xd7{\x18\xf6N\\\xde\xc5R=\x85PM\xd9B\x9f \x1e\x93G\xb8\xff\xe2l\x12W&\xa4\xe8J\xa6\x04\xab#\xfa|\xe3\x8a*?\xbd\xfee\x9e\xb1J\xb3\x1d\xfe\xaa|ݭ%L\f{\x0f\x8a\xae\x05Y\x11/\xd7\xe3\xb7\x17)\x1d\x1a*\x80\x8d\x80u93'՟h\x95)!2s\xfe\xac\x15\v\x9by|\xfc\xd5o\xc0\xf2\n\xd7\x1f\x1a\u007fq\xba\xaa\x996H،\x1b\xf3\x836\xf4߽zN\xc56T\xd8\xf3O\xe3ukty9\xee~\xf6\xac\xd5\x1f\x06U\xc4\"\x8a\x96\x18\xf5KzT\xcf1\xeb\x11ɟ\xf2\xa4C>\x05\xa7WHх,\xdcw5\xaf[\xe6gJjO\x95\x9as\xe5Ֆ\x8b\xcd\xf9*l\xa1\xb4d\xc8\xeej\xb4\xab\xd1\x13*\xb4\xb9\x9a6/\xab7\xe7\x93Q\x06\xe5>\xe7\xe9t{:\xc2\x15u\xd4e\xaf\xde\\[\xf6뙙6\xe1%\xa9H;p~\xa4\xb3d\t\x1a\x96\x80\a\x94\xa0\xa4\xcboq\xd5o|\xe1\xd1\xf1\x98\x04\xd4>\x94\x90@\xd3\xd4B\xb12\x9e\xf0\xa8\xb3B\xb1\xcaG'\xbf\xf4\x01\xf5;3\x03\xb3-\xe4\x96@©\xc0\xf4\x8a\xe5\x06Jfq\x95\x04\x9a%\xfb\x92\xccV\x18>dt\xf3\xdeZ\xf2\vR\xb6\xf2\xb8`\xe0\xd4Ȩ\u007f\xad\xb2L\x80l\xaa\x8dW\xe8,vH\xd1\xef\xa4l\xa0\t\x19O3\xc7\xcbo\x8cK\x8b\xbb\x93\x98\xe2\xe9\xcen#\xff\x9c\xbd\xb3v\xe4\xd4\xceLS\x14h̶\x11\"eڷ\x9c\xfb\xfa\xdbt\xb9|\x8b5\xce\\'/\x02]\"`,\xa3\xe73\x01+4\x86\xedbq\xb3g\xd2@;\x94\xe8\f\x9fT\xbc\xd1;\x86]\xe6ذ\xb4\x97\x8f`\xb1\xc26,L\x10o\xfe{\xbdޥ\xec\x02\xa1v\xbe\x00\"\x8f\xe5_\xa3j>\x13'_k\xaesT\xf9Ƕ#\xe1\xc6\x05\x9f\x1d!\xbar\xbd(\xf8\x8e\x93\x1e$\"\xed\x98ް\x1d\xae\n%\x04\xba4\xf3\xd3u\xbd\xe5a\r\xf9y\x9f\x91\x99ŭ\xfd\xdc\xef\x1b\"\x1d\x9eھ2\x06\xf3\xe5\x15]\xf5V\xcb5v\xe5\x90O\x16\xa4\xdc\xc4g\xa9n\x8f\x85d\xe1\xe0ӕ\xf6\xfb\xc6\x03\x16䪇\x13\xeb\b_\x05c0\xed\xcdV\xecw\xa5\xaf\xa0\xe2\x92\xfe!\x8b߅\"\xe2\xe0\xb3\xd6\xefj\xd6-\xac\xfb\x9e\xfa\xb4i\xd2=E\x8a\xf1@L\x99\xaa\xe9\xd4\xd8\x15|\xc2S\xcb\xcag\xbbb\xe9\x82o\xa9j\xc9\xd4\xe5N\xdek\xb5#\u007f8\xf1\xb2\x15^\x89w\xf7L[΄8\xfaI&gO\xbc\xf8\x80\xa4\xb8&\xad\x974Z\xc3*\x970\x1b\xbau\xae7\x97\x9e\x13\\\x9e\xeaF5v J:Q\x94\x0e\xfb;`k\xf8\xa4,ƈ.\x1f\xc2$\xe1\x8bƮp\xbbU\xdazO\u007f\xb5\x02\xbe\r\xd6P\x02.\x9d\tw#\xe5\x8b\x17\x03\xb7ݥ|ǽ\xce\xd1\xd1\xee\x10\xba\nO\x15;\xfa\x9cEV\x14dl㵱L$\xe4\xdb7\xe5@9\xb3\x93\xb8\x0f˿$\xec\xb0\x13\x84\xdf\xf5\xfb\xb7\x1f\x8e\xb7\xdá\xf3\x98s9\xe5^\xb6'5\x1d\xb8Lc\x94𬹵$O\xfbWv`I\x82\n\x01\x86d\xcaD\x99\xc09\xc9\xeeޓ\uef5b\x0e!\x0e\xfd\x9b\xb6\xf3\x94\xea\x0e\x9bSD\x96\x8dC\xc1Ķ\xfc7K\xdcıD\xcab\xcf䎘J\xabf\xb7\x8f|9\xa1\x19\xa7\"p\r-\nj\xd1\xec\x88\xd5\xc3u\x89m\xb4\xec\x85`\xc2\x05J\xd9[.+\x9e&W\x1aB±\x80\xfeu(\xfc\xb7\xdajU\xad\x02-\xdc-\xc7U\b\x8dh\xae\xc8\xfe'G~\x02hWa˱A]\xa3\x04f\xc2z2>\xa8\x9a'\xeb\\\x9c\xc22ms\xbd\x8a\x87A\xe7\x05\x87\xc2AN\xaf\xf7!\x04~\xfc\x87e\xb7\xe3\x9f2\xb8\x02\xc3e\xac\xdd\xef\x03K\x9e\x15\f\xf9\x19\x1a\x9d\xaf\x9e\xbc\xc0:\xf1\x10\x06\xfe\xc0p\xf9\u007f[W\xe0\xd0j\x98\x8f96\xe5\x97Q\xf7Qv\xae+Q\xddv\tv`\x02\x1f\u007f\xe2[\u007f\xa7VЪ\xff\xfcwϺ=d\xd9,\xeff\xcd\x15g\x89\xb4v\xc7BA\xea{\x81dG\x18ġ%\xf4\xee,\x93\xf7\xf02'\xee5=\xb8\xf8\xb3\x12\xaf\xe3\xd7\x1c^滽\x99\xe3\xf6\xba\xbb{f\xae\x94\xfd\xd2\x19\xfb\xb7\xd0-\xe1\xb9\x05\b\t\xdf-\xb1\x8d֛[\xf4\xddz\xae[\\\xe3D\xb5\xeb\x91;\xf7J\xce[R\x0f\x9c\x8aU_\xbdٓ\xe9t:a\x8d|F\xeb\xa4\xd1s`\x8dğ=j\xfa媗\u07fbJ\x9a\xd9\xfe\xfbɋ\xd4b\x0e\xcb༩\xbf\xa23\xc1r\xfc\x84\x1b\xa9\xa5\x97FOj\xf4L0\xcf\xe6\x13\x00\xa6\xb5\xf1\x8c\xa6\x1d\xfd\x04\xe0F{k\x94B;ݢ\xae^\xc2\x1a\xd7A*\x816\x12/W\xef?T\xbf\xab>L\x00\xb8\xc5x\xfcI\xd6\xe8<\xab\x9b9\xe8\xa0\xd4\x04@\xb3\x1a\xe7\xb0f\xfc%4\xce\x1b˶\xa8\fOwU{ThM%\xcd\xc45\xc8\xe9\xea\xad5\xa1\x99C\xbb\x90(d\xb6\x92H\x1f#\xb1U\"v\x9f\x89\xc5u%\x9d\xff\xf3\xe5=\xf7\xd2\xf9\xb8\xafQ\xc12u\x89\xad\xb8\xc5\xed\x8c\xf5?\xb4WOa\xedTZ\x91z\x1b\x14\xb3\x17\x8eO\x00\x1c7\r\xce!\x9en\x18G1\x01ȘEjS`BD-0\xf5h\xa5\xf6h\x97F\x85Z\x9f\xee\x12踕\x8d\x8f('Y \v\x03E\x1ap\x9e\xf9\xe0\xc0\x05\xbe\x03\xe6`\xb1gR\xb1\xb5\xc2\xd9_4+\xffGz\x00?9\xa3\x1f\x99\xdf͡J\xa7\xaaf\xc7\\YM:z\xec\xcc\xf8#\t༕z;\xc6\xd2=s\xfe\x99))NZ\a\xe9\xc0\xef\x10\x14s\x1e\x8c\xb6e,\x1e?\x97ț\x1c(\xfb[ƪ\x82E\xf6\\\xb3\x81\x0f \xa4\xa3\x02\xc0E\xa2C\xb0\xa8<\xa3\xf59x\x1b\xde$>7z#\xb7C\xa1\xbb5\xcd%\x8b\xb9B\xba\x87\xdc2\xdeD\xa1\x89\xac\xa3\xb1f/\x05\xda)\xf9\x87\xdcH\x9e9\t6e\xae\x8dD%\xdcP\xd2\v^\x16E\xb1(ȫ\x99\xba\xa2\xc3\xe5ic,\x8d\x99\xd4ɂ[\x021\xd8\xd8:\xa7T\xedQ\x8bS5rƍ\x89Qˡ\x80\x83\xf4\xbb\x14\x0e\u0558\xdf\xc1\xab\xbeG\xe3\x05\x8fc\xd3=ޟvH;S\x02Ep\xc8-\xfahm\xa8\xc8|Ȕ*\x80/\xc1ŀڏ\x13e\xc4B\xad\x9c~\xc1\xe3\x10h\xb8\xa6\xdc\\\xc2\\g\xf9\x8eJ\xe7°\xc5\rZ\xd4~4\xa8Sgb5z\x8cq]\x18\xee(\xa4sl\xbc\x9b\x99=ڽ\xc4\xc3\xec`\xec\x8b\xd4\xdb)\x01>\xcd\x1e4\x8bm\xc5\xec\xbb\xf8\xe7\x82\xc8O\x0f\x9f\x1e\xe6\xb0\x10\x02\x8cߡ%\xadm\x82*\x86֩o\xde\xc7\x1c\xfb\x1e\x82\x14\u007f\xb8\xfb\x16\\L\x93<\xe7\x06lV\xd1\xfa\x8fT\xa8E\xa6\b\xa2UҊ\xb1@\x99\x92\x94]gm\xa6X3f\x88c\x15fwP`\xa2\f2\x16Q_p\x18L_q\xb3\\\xec^\xf1\xb1RHK-$\xa7B\xec\xdc7J\x83!\xce\xea\xed\x11\xc1\xfa\x15\xf8\xa5\x880.x\x12 \xe7\xc3+\x1c?t\xf7\xb6mY\nO9\xc79\xf4T@9\xd0H9\x90\xd9!r1(p\xa35y\xa37\xc0N\xa1\xee\xce\xf5c\xfc\x1b#\xc4:\xf0\x17\x1c\x01~ \xcaǸ\xb1`\x9c\x8e\x11/\xc1a\f\xbe\xd7\u0600\xeb6\xce\xd9\x12\xed-\xbc,\x17\xb4\xf1\x94&\x19,\x17\xb0\x0eZ(,\x1c\x1dv\xa8\xa9C\x90\x9b\xe3\xf8]4\x9e\xeeW\x05\xd5Xa\xe4\x1a\xbf`;.C\x8a\xe1sX\x1fGj\x82\x1b\x84l,n\xe4\xcf7\b\xf9\x187\x16\xc0\x1b\xe6w \xb5\x93\x02\x81\x8d\xc0\x9f\x8a\xb5\v\x82\x9e\xf2\xffC\x8e\"ߠ\x9e\u05fc=\xb1\xf3\x16\x87/\x18_\xf1\x9fǼ\xed\x84B\xf9\x9d#\xffy-xɏG%ڟ\x1e\f\xfe\x94*,>\x92*Ϙy\x1e\x9ex\xa5R+\xcf\x16c\xceLu\x81\xb1\x16]c\xb4\xa0\xe6\xe9\xb6:\xade\xf9\u007fW\xad\x8d\xabuz\x1e\xe5zkE\v7\xb5*\xf1\x89\xe6\xcd\xcdJz\xb8\xea\xb6\x02f\xed\xa8Sl\xfb\x95\x9e\x8c\xbfH\x9b\xf2\xaeӧP?\xac!\xe8X\xa9Ō_\xc1\xdf5|\xa2ޖ\xb2\x93\x98\x13\xdfv\xcc\x00\xa4\x03m\x0et\xbcC/\x92\x00\xa3S\xbe\xa6n\x8di\x91\x9b\xe1\xb8t\x90JQƶX\x9b\xfdhƦBӢ:\x02sd:\xfb\xdfT\x1f\xaaw\xbfZ\x17\xa4\x98\xf3\xd4Ԡ\xf8\x8a{9|\xe5\x19\xa2{?8Q\x1c\xff\xe4\x0e\xf4\xe3\xc7\xd2,\xcfl\xde\xf6\xe3\b\x18\x1b\xa9\xa8\x16\x1c\x89\x13m\xc50|\x8f\xfc\xb8\xba\xbfs\xb1\x84G\xed\xc7ʾ\x03Z\x8c\x1d\x13\n\xaa\xe2M~\x97\bΣ\x1d1\x80\x93\xf6\xa2\xceA\x19\xbd\xed9N\x1a\xf9\x95\x82*\xb4dPƂ@O\xa9Io\x81\xef\x98\xdeb\xfb\n\x95\xf9\u007f\x9dS2\x9f\x9eʹ\x16\"\xf5%\xf3\xb8I\xa3Or\xacL\x1f\xbc\x00\xb7\x9b\xc7_\u007f\v\xf7E\xb3\x17ۜ+\xb8\x0f\xf6\x97,M\xa0N}\xfb\"\u070eooo\x87\xcf\xcd7 \xf1ַ\xf0W\xde5\xe0\xc0\\\xfb*\xfe\xeb\xe1PS\xb5z\xb5\x04\xfe\x92v\xa5\xe7\xc3|\x04\xd8\xda\x04\xff\x9agލ\x19t~\xee\u007f\v\x8f\xf1#Ƶ\"\x83\xf6\x14\x8d\xf0`\xa9\x95l_\xc5bP\x18\xcb-\xb7?/-z\xdfZ\xbak\xc3/17\xc85\x9ak\a\x93)_v\xf4\x9aA\xee΄\xf5\xe9\xa5x\x0e\xff\xfeϤMה\x13\x1b\x8f\xe2\x87\xfeǵw)d\x94/d\xf1'\xa7:&}\x1d\x84\xbf\xfdc\x92\xaeB\xf1\\>i\xd1\xe4\u007f\x03\x00\x00\xff\xff\x1d\r\x93\v\x97\x1c\x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4\x96Ms\xe36\x0f\xc7\xef\xfa\x14\x98}\x0e{y$\xefN\x0f\xed\xe8\xd6\xcd\xee!\xd36\xe3I2\xb9tz\xa0I\xd8\xe2F\"Y\x00t\xeav\xfa\xdd;$%\xbf\xc8v6=\x947\x91 \xf0\xe7\x0f\x04Ī\xae\xebJ\x05\xfb\x84\xc4ֻ\x16T\xb0\xf8\x87\xa0K_\xdc<\xff\xc0\x8d\xf5\x8b\xed\xc7\xea\xd9:\xd3\xc2Md\xf1\xc3=\xb2\x8f\xa4\xf13\xae\xad\xb3b\xbd\xab\x06\x14e\x94\xa8\xb6\x02P\xceyQi\x9a\xd3'\x80\xf6N\xc8\xf7=R\xbdA\xd7<\xc7\x15\xae\xa2\xed\rRv>\x85\xde~h\xbeo>T\x00\x9a0o\u007f\xb4\x03\xb2\xa8!\xb4\xe0b\xdfW\x00N\r\u0602\xc1\x1e\x05WJ?\xc7@\xf8{D\x16n\xb6\xd8#\xf9\xc6\xfa\x8a\x03\xea\x14xC>\x86\x16\x0e\ve\xff(\xaa\x1c\xe8sv\xf5)\xbb\xba/\xae\xf2joY~\xbaf\xf1\xb3\x1d\xadB\x1fI\xf5\x97\x05e\x03\xb6n\x13{E\x17M*\x00\xd6>`\vwIVP\x1aM\x050\xf2\xc82kP\xc6dª_\x92u\x82t\xe3\xfb8Ldk0Țl\x90L\xf0\xb1\xc3|D\xf0k\x90\x0e\xa1\x84\x03\xf1\xb0\xc2Q\x81\xc9\xfb\x00\xbe\xb2wK%]\vM\xe2\xd5\x14\xd3$d4(\xa8?ͧe\x97\x04\xb3\x90u\x9bk\x12X\x94D\x9eD\xe4\xb8\xd6;\xa0#\xbe\xa7\x02\xb2}\x13:ŧ\xd1\x1f\xf2µ\xc8\xc5f\xfb\xb1\x90\xd6\x1d\x0e\xaa\x1dm}@\xf7\xe3\xf2\xf6黇\x93i8\xd5z!\xb5`\x19Ԥ4\x81+\xd4\xc0;\x04O0x\x9a\xa8r\xb3w\x1a\xc8\a$\xb1\xd3\xd5*㨪\x8efg\x12\xde'\x95\xc5\nL*'\xe4\fm\xbc\x04hƃ\x15\x98\x96\x810\x102\xbaR`'\x8e!\x19)\a~\xf5\x15\xb54\xf0\x80\x94\xdc\x00w>\xf6&U\xe1\x16I\x80P\xfb\x8d\xb3\u007f\xee}s:g\n\xda+9\xe4g\x1a\xf9\xd29\xd5\xc3V\xf5\x11\xff\x0f\xca\x19\x18\xd4\x0e\bS\x14\x88\xee\xc8_6\xe1\x06~I\x98\xac[\xfb\x16:\x91\xc0\xedb\xb1\xb12u\x13\xed\x87!:+\xbbEn\fv\x15\xc5\x13/\fn\xb1_\xb0\xddԊtg\x05\xb5D\u0085\n\xb6\xce\xd2]\xee(\xcd`\xfeGc\xff\xe1\xf7'Z\xcf.H\x19\xb9\xd0_\xc9@*\xf3\x92\xf6\xb2\xb5\x9c\xe2\x00:M%:\xf7_\x1e\x1ea\n\x9d\x931\xa7\x9f\xb9\x1f6\xf2!\x05\t\x98uk\xa4\x92\xc45\xf9!\xfbDg\x82\xb7N\xf2\x87\xee-\xba9~\x8e\xab\xc1\nOW2媁\x9b\xdcbSQ\xc7`\x94\xa0i\xe0\xd6\xc1\x8d\x1a\xb0\xbfQ\x8c\xffy\x02\x12i\xae\x13ط\xa5\xe0\xf8\xef07.Ԏ\x16\xa6\xf6}%_\x17\x8a\xf6!\xa0N\x19L\x10\xd3n\xbb\xb6:\x97\a\xac=\xc1Kgu7\x15\xed\x8c\xee\xbe\xc0\x9b\x93\x85\xcb\x05\x9dơM\xceW\xae\x1e\x1er\xee,\xe1\xec\x16\xd6p\xd6s_璛\xe1\xbf$S:\xf1\xc8FG\"trԟեMoe\x81D\x9e\xcefg\xa2\xbed\xa3\xfc\x04P\xd61(\xb7\x1b7\x82tJ\xe0\x05)\x95\x81\xf61\xf5\x194`\xe2\x19\xbf\x11\xcb\xf1\xbf$\x90\xd7\xc8ܜ\xd9Y\xc1ႦW\xb2\x93Fz^\xa8U\x8f-\bE\xbc\x92YE\xa4v\xb3\xb5\xfc\xcf\xfa\x06\x82e\xb2\xb9\x94\x83\xfd\u007f\xfa\x9bIȸ]\x1c\xce#\xd5p\x87/\x17foݒ\xfc\x86\x90\xe7W>-.\v\xbd\xfdc\xe0\r\x94.^ʳIN\xfd\xce\x1cQd\xf1\xa46\xc7\\9\xae\xf6\xfd\xbb\x85\xbf\xfe\xae\x0e\xf7Zi\x8dA\xd0\xdc\xcd_i\xefޝ<\xb7\xf2\xa7\xf6\xae\xbc\x8c\xb8\x85_\u007f\xabJ(4O\xd3\xeb)M\xfe\x13\x00\x00\xff\xff--\nM\xde\n\x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4WM\x8f\xdb6\x10\xbd\xfbW\f\xd2CZ \x92\x13\xf4\xd0·v\x93âi\x10\xd8\xe9^\x8a\x1ehj,\xb1K\x91,g\xe8\xcd\xf6\xd7\x17CJ\xfe\x90\xe5\xdd͡\xbc\x89\x1c\x0e\x1f\x1f\xdf.\xbd\u007f[\xffT\xbf]\x00\xe8\x88y\xfa\x17\xd3#\xb1\xea\xc3\n\\\xb2v\x01\xe0T\x8f+h\xfc\x83\xb3^5\x11\xffIHL\xf5\x1e-F_\x1b\xbf\xa0\x80Z\x16m\xa3Oa\x05ǁ2w\x00T6\xf3~H\xb3.i\xf2\x885Ŀ͍~4CD\xb0)*{\t\"\x0f\x92qm\xb2*^\f/\x00H\xfb\x80+\xf8$0\x82\xd2\xd8,\x00\x86\xbdgXհ\xbb\xfd\xbb\x92Jwث\x82\x17\xc0\at\xbf|\xbe\xbd\xfbqs\xd6\r\xd0 \xe9h\x02g\x06'\x98\xc1\x10(\x18\x10\x00\xfb\x03(P\x0eTd\xb3S\x9aa\x17}\x0f[\xa5\xefS8d\x05\xf0ۿQ3\x10\xfb\xa8Z|\x03\x94t\aJ\xf2\x95P\xb0\xbe\x85\x9d\xb1X\x1f&\x85\xe8\x03F6#˥\x9d\x88\xeb\xa4w\x02\xfc\xb5\xec\xadDA#\xaaB\x02\xeep\xe4\a\x9b\x81\x0e\xf0;\xe0\xce\x10D\f\x11\t]\xd1\xd9Yb\x90 \xe5\x86\x1d\u0530\xc1(i\x80:\x9fl#b\xdccd\x88\xa8}\xeb̿\x87\xdc$\fɢV\xf1(\x87c3\x8e1:ea\xafl\xc27\xa0\\\x03\xbdz\x84\x88\x99\xa7\xe4N\xf2\xe5\x10\xaa\xe1w\x1f\x11\x8c\xdb\xf9\x15t́V\xcbekx,*\xed\xfb>9Ï\xcb\\\x1ff\x9b\xd8GZ6\xb8G\xbb$\xd3V*\xea\xce0jN\x11\x97*\x98*Cw\xb9\xb0\xea\xbe\xf9.\x0eeH\xafϰ\xf2\xa3Ȍ8\x1aמ\fd\xcd?q\x02\xa2\xfa\"\x982\xb5\xec\xe2H\xb4t\t;\xeb\x0f\x9b/0.\x9d\x0fc\xca~Q\xcea\"\x1d\x8f@\b3n\x87\xb1\x1cbV\x9e\xe4D\xd7\x04o\x1c\xe7\x0fm\r\xba)\xfd\x94\xb6\xbda\x1a\xc5,gU\xc3Mv\x1a\xd8\"\xa4\xd0(Ʀ\x86[\a7\xaaG{\xa3\b\xff\xf7\x03\x10\xa6\xa9\x12b_v\x04\xa7&9\r.\xac\x9d\f\x8cNv\xe5\xbc&\xa5\xbe\t\xa8\xe5\xf4\x84@\x99ivF\xe7Ҁ\x9d\x8f\xa0\x8e\x95?\x10X\x9fe\x9e\xaf\xdc\fN\xc5\x16y\xda;\xc1\xf2%\a\xc9\xf2\x0f\x9d:7\x9a\xef\xb1nk\xf1\n\x1a\x80\x14\xf7\xf8\xa1\xbe\xc8x\x1d\x03̪w\x16\xc9(b\xa1Ax\x15+\x10\x93:\xc5t\xb9\xb44t\xa9\x9f_\xa0\x82_3揾}r\xfc\xc6;\x16\xb9?\x19t\xe7m\xeaq\xe3T\xa0\xce?\x13{\xcbؿ,r\xbc\x90\x0f\x97\xd4e\xe0\x1a\xc5\xca\xf1\xfa&\x86\x805R\xb2W\x97\xbb\xd9\xdc~\xcb>\xae\x84?\xc9ԕ\xda\x19[\xbe#\x9f\x17\x82ܲ\xa3\x10dJ\xb98\x10\xe4\xed\x11\x1d2\xd2\xd1\xc3\x1e\fw\xb3\x19\x01\x1e:\xa3\xbb<1\xabH\xec\x91\xc8k\x93\xcd\xe6\xdb\xe1K\xf1\x99\x883J\xae\xb2\xc2g\xba\x05\xfcE\xf7\x15˸\xb6@5\x94\xf1\x8bl\x87\x15'\xfa\x06\xe3\xc9\xf1#\xd5:ň\x8e\x87,\xf9\"\x9eNx\xa9\xf3\x8c\xe5\xfa\xc7\xfa\xe33\xf6\xf3\xfe\x18\x99\x9f\x9aʸ\x82&D\xacȴ\xf2|\x9011\xa0l\f\x97d\x94v\xfe\x9c9'j\xf6D\xf1k01\xdb\xec3\x10?\x1c\x02\x8bK\xa2+7\xe0\xf4\xc1\x96\x13\"\xe5ׅV\xd3w\x8d\xb4-B\x83\x16\x19\x1b\xd8>\x16\xbb\u007f$\xc6\xfe\x12\xf7\xce\xc7^\xf1\n\xe4f\xac\xd8\xcc\xc8H\x1e\xd5jkq\x05\x1c\xd35\x95\xcdn\xf38\xfa\x01\xed\xe4\xfa\x8b\x99\xdb\x1e\xd1~[aޞ\xc3\xf4\xee\xfb\xe0@\xcb\x1a[\xb6\x8e+\x95F\xf9\xf6\xf6\xfa\xfe\xcfw\xa3a\x00m\x94F\xe3Dr\xe8\xe1\x1bı\xc1(\x8cY}A\x04\xc3*\xe0\x14\xc0\xd0\x06\xab\bc\xc8#\x86 \x0eaIu\rZ\x94nȒ\xf4\xa9-0\tj\xf3\v\x96\xae\x80;4D&\t\xa6Tr\x87Ɓ\xc1RUR\xfc\xb7\xa7mI\xd7\xe8І9\x8cq\xe5\xf0y\xd7/Y\x03;\xd6t\xf8\x1a\x98\xe4в=\x18\xa4S\xa0\x93\x03z~\x89-\xe0ge\x10\x84ܪ5\xd4\xcei\xbb^\xad*\xe1R\xfc.U\xdbvR\xb8\xfdʇb\xb1\xe9\x9c2v\xc5q\x87\xcdʊj\xc9LY\v\x87\xa5\xeb\f\xae\x98\x16K\x0f]\xfa\x18^\xb4\xfc\x1b\x13#\xbe\xbd\x18a\x9d)F\xf8|x=!\x01\n\xb0 ,\xb0\xb85\xdc\xe2\xc0\xe8\xe4 \xdf\xff\xe3\xee\x03\xa4\xa3\xbd0\xa6\xdc\xf7|?l\xb4\a\x11\x10Ä\xdcbt0[\xa3ZO\x13%\xd7JH\xe7\u007f\x94\x8d@9e\xbf\xed6\xadp$\xf7\xffth\x1dɪ\x80K\x9fԐ\xc3\xec4i./\xe0Z\xc2%k\xb1\xb9d\x16\xbf\xba\x00\x88\xd3vI\x8c}\x9a\b\x86\xf9\xd8tq\xe0\xda`\"%MG\xe45Ʉ\xee4\x96$=b \xed\x14[\x11=\x14\xb9s6]^\x8c\b\xe7\r\x97\xbe\xacw\x9a.\x82\\p\x99\xecI\xd8\xe4\xc0\xa7&\x87\x19VΈ\x024S/\xdb\xef\x19F.\x1b\x1dl1\xa3pD\f\xf4I\xc5\xf1\xcc=n\x14\xc7\x1cl\xda\n\xaefA[)\xe3#\u007f\xd4I9?\x85>%\x9f\x05L+~\x06W<\x91\x81\xc1-\x1a\x94%&\xc7u*\x9d\xc9 \x1b&\x1as\x8cǕ\x02Nx\xf5,ⷷ\xd7ɓ'&F\xecn~\xee\x19\xfeз\x15\xd8p\x1f\xe8Ο}q\xbd\r\x87y\x9f\xe6\x140\xd0\x02Cb\xda\a\t\x10\xd2:d\x1c\xd46K\x91\xca' \xc37\x18w\xbc\x0e\x1e,\xba\xcaCh!\xde\x03#\xdf)8\xfc\xeb\xee\xdd\xcd\xea\x9f9\xd6\xf7\xb7\x00V\x96h}^\xee\xb0E\xe9^\xf7\xa5\x02G+\frJ\xfc\xb1h\x99\x14[\xb4\xae\x88g\xa0\xb1\x9f\xde|\xces\x0f\xe0Ge\x00\xbf\xb0V7\xf8\x1aD\xe0x\uf593\xd2\b\x1b\xd8\xd1S\x84G\xe1j1\r\xa6=\aH\xbd\xe2\xb5\x1f\xfdu\x1d{@P\xf1\xba\x1dB#\x1ep\r\xaf|Zs\x80\xf9+\xd9\xceo\xaf\x8eP\xfdS0\xedW\xb4\xe8U\x00\xd7\xc7\xe1\xa1\xd1\x1d@\x06\xcb3\xa2\xaa\xf0\x90UM?\x1fT\xc8U\u007f\v\xca\x10\a\xa4\x1a\x90\xf0\x84Iz\xc1Q\"\x9f\x81\xfe\xf4\xe6\xf3Q\xc4c~\x81\x90\x1c\xbf\xc0\x1b\x10\xb1\xd8Ҋ\u007f[\xc0\a\xaf\x1d{\xe9\xd8\x17:\xa9\xac\x95\xc5c\x9cU\xb2ه\xf6\x881\tʄyp\xcdL\ueffa*\x13C;C\x88\xf6\xcb\xd8\xf6[2\xc9\xe9\xffVXG\xe3/\xe2`'\x9ed\xbe\x1f\xaf\xaf~\x1f\x05\xefċl\xf5H\x02\x1etd\xd8\xe58\x93\x98\xbd\x1f-N\xa9c&c\xed\xd7<+3t\xacʤb\xc3\xf6䩄\xed$\aƭ\x18VY`\x06\x81A\xcb4I\xee\x01\xf7\xcb\x10\xe25\x13\x14\x9f)\x04\xf7}\x0e`Z7\"\x1b\x8ac \x8fIh\xe4\x04\x15ڬ\xb2\xc7\ue795ð\xafsF\n\x1f\aK\x93\f\xcet\x96\\\x9d\xb3\xd4Q\xbfi\x8e\x16e\xd7Ρ,\xe1Ai\xc12\xe3\x06\xad\x13ef\xe2\xd5<\xd38!\xac\xc0\xcb3<\x88-\xe8L\xf1\x12E\x112\xbd\xbe\x80\xf1]\xc7\\\x85p\xbc<8\n\x91*t\xca[\xc7\x10\x97\xf9Rr\xb2\x86J\xabɐV|1ed\xa6\xf3\x98&G\x9d\xd1!\xd2y}\xed\x1b\xdeϨ\xb0C#?\xf24\xf8S\x97\xda\xfbTL\xbc\xb4\xc6.\x15\xe5\xe9㧕\xd3⽜\xef\xf0\xed,ã\xba\x8b\x96\xacw\xd0\xf6\x8fg\xe4\x8ad\x18\x90\v;}\x04#j\xc8}\x12M9\xfe\x96\x89\x069\xa4\xb7\x9d\xe9\x9e\f\xd5!\x95\rn\xc9\xdd\a\xd3K\xa5i\x84\xd7'\xaa5\x82\xf5}\xa2\v{\x82fg\x91\xfb\x9eF\x86\t\xf3\xe4u\xabL\xcb\\\xe8k.\xb3De\xd74l\xd3\xe0\x1a\x9c\xe9\xe6\xd3',\xb1EkYu\xce\x14\u007f\x0e\xabB\xc5\x1e\xb7\x00ۨ\xce\xf5%\xfb\xc8=^بS\xcf\xeb\x1ad\x8b\xe1\xb1:3*VlLڛ\xc6\xef\x19:\x82Ã\xa0G\xb5\xc1|\xd0\u007f\x89O\x00\xf0\x0fZ\xe7\x10Қ\x9c\x81\xf5\xde뤅\xc1\t\xa7|\x83\x8f\x99\xd1\xd9C\xdcp\xf22\x99Lf\xeeGo\rϺ\u007f<\xe8\x1c\v\xe22\xa8U\x93\x8cY9ր\xec\xda\r\x1a\xe2\xc3f\xefЎ\xddy\xae?\xe3\xeb\xba\x03\x1b\a\xfb\x93\xfc\x02\xa5X\xaa\x96L\xfa>*Y\x97S\xc0\x85\xd5\r\xdbg\b\xa7\x8b\xf8܍\x8c\x8b\\\xc0A\x9f\x93Qk4~\xea\xb9}%\x8f\xe9J\xc9#\x95F\xb2g!\xdd_\xffr\"\xd3\x13\xd2a5\t\x0eq\x9e\xd8\xf9\x03\x9d\xf2uN8\x91\xc4Xɴ\xad\x95\xbb\xbe:\xa3\x05w\xfd\xc2d\r\xb3\xe79\xec\xa9EUȉ\xaa\xf7-\xcf2\xd5\xf1\x13\xf09\xa8\xa3\xc5g\xa2P||\xceŠ;\xd4̐\xa5\xfb7\x81\xcb\xe9\xa3\xd5k\xb0\xc27:)\xf3\f\xa9hhCX\nN\x94Z)\x83\x19\x97\t\xf3\xb02\n\"c\xf8\xbfg\xfc\xc8\xea\xc9l\xd0#\xe7\x03ڱY>\x1c\xe96\xfdC\xd0\x1a~\xfdmqHlXI\xc5\x13\xf2\x9b\xe9\x1fYĔ3\xfdՄ\xffY*\x19*\t\xbb\x86O\x9f\x17\xe9\xd9\xf2>\xfd1\x04\r\xfe/\x00\x00\xff\xff\xb0\xddǼ\x99\"\x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4YKs\x1b\xb9\x11\xbe\xf3Wti\x0f\xcaV\x99\xc3]'\x95\xa4x\xb3\xa5lJɮ\xac2e]\\>\x80\x83\xe6\f\x963\x00\x02`H3[\xfb\xdfS\x8d\a9/\x92\xa2*Z\xcf\xc5\x16\xd0h|\xf8\xd0/4'\xd3\xe9t´xBc\x85\x92s`Z\xe0W\x87\x92\xfe\xb2\xd9\xfa\xef6\x13j\xb6\xf9q\xb2\x16\x92\xcfᦱN\xd5\x1fѪ\xc6\xe4x\x8b+!\x85\x13JNjt\x8c3\xc7\xe6\x13\x00&\xa5r\x8c\x86-\xfd\t\x90+錪*4\xd3\x02e\xb6n\x96\xb8lD\xc5\xd1x\xe5i\xeb\xcd\x0f\xd9߲\x1f&\x00\xb9A\xbf\xfcQ\xd4h\x1d\xab\xf5\x1cdSU\x13\x00\xc9j\x9c\x83V|\xa3\xaa\xa6F\x83\xd6)\x836\xdb`\x85FeBM\xacƜv-\x8cj\xf4\x1c\x0e\x13aqD\x14N\xf3\xa0\xf8\x93\xd7\xf31\xe8\xf1S\x95\xb0\xeeߣ\xd3?\v뼈\xae\x1aê\x11\x1c~\xd6\nY4\x153\xc3\xf9\t\x80͕\xc69\xdc\x13\x14\xcdr\xe4\x13\x80H\x80\x876\x05ƹ\xa7\x94U\x0fFH\x87\xe6\x86T$*\xa7\xc0\xd1\xe6Fh\xe7)\xdb\xeb\x01\xb5\x02W\"m\xe9\xe9fB\nY\xf8\xa1\x00\x01\x9c\x82%BD½2\x80_\xad\x92\x0f̕sȈ\xb8L+\x9eɤ3\xca\x04\xce\xef{\xa3nG\xe7\xb0\xce\bY\x1cC\xf6\u007f\x06\xd5\xc1\xf3\xa0\xf83\x91<\x96\xe8e\x12\x9aFW\x8aq4\xb4y\xc9$\xaf\x10\xc8r\xc1\x19&\xed\n\xcd\x11\x14i\xd9\xe3Nw\x91|J\xfaZ3\x97\xb0s\t\x15A\xb6\xb3\xfdS{\xe8ܾ\x0f\x8a\xc7\x05\x10\x8d\x1a\xacc\xae\xb1`\x9b\xbc\x04f\xe1\x1e\xb7\xb3;\xf9`Ta\xd0\xda\x11\x18^<\xd3%\xb3]\x1c\v?\xf1\xba8V\xca\xd4\xcc\xcdAH\xf7\u05ff\x1c\xc7\x16\x17eN9V\xbd\xdf9\xb4\x1d\xa4\x8f\xfdဖ\x9c\xad\x88\xd7\xffM\xe0.\tҭ\x92]^\xdf\xf7F\xc7\xc0\xb6\x94\xa6@\x9c\r\x82hG뻢\xab\x8f3\x17\x06\xc2\xf4\xe6\xc7\x10\xca\xf2\x12k6\x8f\x92J\xa3|\xf7p\xf7\xf4\xe7Eg\x18@\x1b\xa5\xd18\x91\xa2k\xf8ZY\xa55\n]f\xafIa\x90\x02N\xe9\x04mp\x8a0\x86\xa3f5\xff\xce\xc4\xfck\xaf;X\aN\x17>\x9f\xebN\xdc\x00%;\x10\x16X\\\x1aNq :\x85\xec\x8f\xffX\xdc\xcf\xfe9\xc6\xfc\xfe\x14\xc0\xf2\x1c\xad\xf5\xf9\x1ak\x94\xee\xcd>gs\xb4\xc2 \xa7\xc2\x05\xb3\x9aI\xb1B벸\a\x1a\xfb\xf9\xed\x97q\xf6\x00~R\x06\xf0+\xabu\x85o@\x04\xc6\xf7\xe1/ٌ\xb0\x81\x8e\xbdF\xd8\nW\x8a~\xd2\xda3@\xd6\x15\x8f\xbd\xf5\xc7ul\x8d\xa0\xe2q\x1b\x84J\xacq\x0eW\xbe\x12<\xc0\xfc\x8d\x1c\xeb\xf7\xab#Z\xff\x14\x1c芄\xae\x02\xb8}\xbek{\xe4\x01\xa4+\x99\x03gDQ\xe0\xa1\x10\xed\u007f>xSH\xfc\x1e\x94!\x06\xa4j\xa9\xf0\x8a\xe9\xf6B\x00\xfd\xf9헣\x88\xbb|\x81\x90\x1c\xbf\xc2[\x102p\xa3\x15\xff>\x83Go\x1d;\xe9\xd8W\xda)/\x95\xc5c\xcc*Y\xedB\xb5\xbfA\xb0\xaaF\xd8bUMC\xbd\xc1a\xcbv\xc4B\xba8\xb27\x06\x9a\x19w\xd2ZS\x95\xf1\xf8\xe1\xf6\xc3< #\x83*|\xbc\xa3\xec\xb4\x12T5P\xb9\x10r\x9e\xb7\xc6A\xd2L\x9fm\x82\xf98\x05y\xc9d\x81\xe1\xbc\b\xab\x86\xb2Pv\xfd\x12?\x1e\xa6\xfe\xf4\x8d\x94\x00\xfd\xc0\xf1͒\xe83\x0f\xe7+\xd5g\x1c\xae\xfd\xd6:y\xb8u\xb3D#ѡ?\x1fW\xb9\xa5\xa3娝\x9d\xa9\r\x9a\x8d\xc0\xedl\xab\xccZ\xc8bJ\xa69\r6`g\xfe\xc9<\xfb\xce\xff\xf3\xe2\xb3\xf8\xd7\xf5s\x0f\xd4y\xf4\xbf\xe6\xa9h\x1f;{ѡR\xad\xf8\xfc2q5\xacTN0\x11\f\xe0\f\a\xb1\x994\xf22\x8a\xf6\x13*E?B\xaf\x11oE\xe3!\xf6R\xbb\xa2\x874\x95\xbd]\x84\xd3\xf1\xf7^OF+>\xe9\x93\xd6v\xc9\xde\xe4\xc1\xa1\xfa\x13][\xed\xcdv\x9a\x9c\xed\xd3\f\x9fʾ\x83v\xc9c9t\xed\"\xef!f\xbb\xd4ˣ\aˋ\x9f˹\xa2\xc7@\xf7W\x8b\xd36p3\\\xe1{S\x86G\x9f\x105\xfa7hh8n\x99M\x9b\x8c\xdd7\xb4\xf4\x85\xa5>O\x92:\xe4\xbeT\xa7\x97Ċ\x89\n9\xec\u007f7\xf1\xcdq\xeb\x9b4\xd7c\x95iR\xd4X\xe4>n\x8c\x80\x1e\xaeK}O\xce\x1cNI\xc5@B6UŖ\x15\xce\xc1\x99f8}½j\xb4\x96\x15\xe7\xfc\xeb\x97 \x15^\xf1q\t\xb0\xa5j\xdc\xfe\x19\x1f\x1d-Rqm\xa3\x15\\\xd6J(\x99=\a\xe5\x81d\xc6,n\xef\xf2\xa7M\x0eN\x84\xb2{\u070e\x8c\x0e\xfa\xd0\xedɛdB#s?y븈\x80\xb8\xd19\x0e\xa2\x18\x94\xaaJ֭\x1c%\xa5\xa6^\xa2!\"|\xf3;1\x92\x02\xc7X_Ŀ\xa7\x0eL\x1e4\xa4X\x18T\xc5\x17bΤo\x13\x92\xfd:\x05\\X]\xb1݈\xdet\x12_2\x91\xf9\x92\x1f\x1d,&y!\xb9\xbf\x9f\xbb\xb4\x9f\xb3o\xee\x8f\x17tc?\x15\x8c\xddB\xbb\xefߛ\xdf\xff\xaa\xf1:;\x9c(\xe2\xacc\xc6=7\xec-:\xc2\xe7\"\x9eW=\x1e\xefڡk\x18\xa8\xba\xdb\xfc\x911j\x94\xa8\xc1\xa0G\xce[\xbac/\xb4=\xd2,\xf7\x9d\xfe9\xfc\xf6\xfb\xe4\x90\xeeXNU;\xf2\xfb\xfeOڱVI\xbfP\xfb?s%\xc3O\xcav\x0e\x9f\xbfL 6M\x9f\xd2\xcf\xce4\xf8\xbf\x00\x00\x00\xff\xffe\xe5\xd5&\b \x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xdc\x18\x93Z\x98\x92\x9a\x01fAW֬#D{zSI\xa4\xbd\x16ĺv\xacV\x95\x1f{h\xe0:\x14\x8fS\x04V\xcc`\x0e*\x88}%Є\xb5r\xc7\xfeV\xb1\\\x8d\x82n\x90\xf7\xae\x86`+\x14`P`f\x95\x1eR2\x85\x9e\xfeIQ\x96#t\x8c\xa8;\xfc\xb7\x88M\x80\x04\x12\xf3\xe7-϶\xde\v \xd9tp Wh\x9c\xe6 Ou?\x86$\xcc\xf1>,2\xa5;\xdag\xe6L\x1d\u008b\xe9\x93\xf6Iз\xed3\xa3y\a\x9a%\xbc\x8f\x9a\xce\xf6\xf9\xf7$lmJN\x10ڻ\xc1\xd4\xd7\x15Z\x17U\x91\xb7\u007f\xb7\x06,J\xbb\xbf\x02n\xeb\xb7s\x10\x99\x10\x9d\xf5\xffƌ9^\xe2\xef\x0eg\xbe\xaa\xc4Ore\x0e\"q\xa5Y\xfeo\xc8\x14g,\x1e\x83\xadHfȯ\xddYW\xc0\xd7\rC\xf2+XsaQ\x1fp\xe6E\xe7\xe55\x88\x91b\xef\xe8)\x98Ͷ\uffd0gc\xda\fT\"]\x0e'{\x9f\xb8\x0e\x12\xfa\x86y\x06.\xb8\xf8\x95k,|\\\xfc\xc9Q\xb3}\xe3\x02\x8aw\x1f~\xc4|\x8a<\x90&y\x03D\xde\x1dl\xb6\xbbtp\xf4S\xd1\b\xaeO\x134\xf9\x8c\xc7\x150x½\xf7X\x98\x04b\x0e\xb3\xceߍ\x86OC\xe2\xb8ԋw\x8fq\xef\xc0\x84\\\xca\xec\xecTQ\xf0\xcf\x13F\xfc\xfd\xd8\xd3# \xed)D\xb8\x9e\x92\xf4\xc2\x11\xc2E\xde\xe9\xc4\x03\x97\x17\xabu\xd1\xb7]o\x87@\x9c\xd5ۡ\x05\x1aCwJj\xe6\xae\a\x80Le\xed8\xbb\xbd7Rs\x84\xe7\xb3B\nP1\xf7I/2\x9f\xc1\x8f\xf6\xbdK#e\xf0(vǻ.I\x9c\xedNH\xcf\x12}Y\xb4\xed\n\v\x97\x14\xd4;\\T\xf2I\xaag\xb9p1\xa5\x99\xcd\xd67\x8b\x9f\xac8\xbe\xa6\xd2\xe8\x8bW\xba\b\xd4\xf6\xf7\fJ!\x99\xcdG\x05\xc6SR0\xa7\x86|\x1b\xebȟ\xb3\xbb\x98Z\u007fbr\xa89\xde\xfa\xfe\xd3\xd4ަ\xbb\xf8\xac\x8e\xff\xf0\xbcE\xbbE]7\xb6.\\\x0foL\xad\xb6\xa5\xc9\xd6\x15n\x9a\x9dH~jo\xca7\xe1\x1d\xb4?\xc5}eY\tqE\x82\xcd*a}+\xaa\xae\"B\x94\xd4\x03\xb4RJ ;l\x8bM)\xa2ϕ\xce\xfb\xfd`M\xe9\xban\bS\xf5\"\x11\f=/}\xd7g\xb7.ۯ\x81\xbb\xecO\xbdӿ\xbcU,\xa1\xbc=SԞn\xa0\x9b\xa2\xd7Pl\xba\x14ke0\x8c\v\x9d\x95\xdf\x14\xf9f\n\xd1\xe3\xe5\xe7\x90lE\xcbvo\x97\xfd\u007f\xac\n\xc5h\x97Y\x88\xa0\xf2\xbcm\xf2\x04\xce\xf2ʜ\xefx^1ѓ\xc0\x0e\xcdZ҂\xd2 \xb9\x88ա\x88\xe6\xf5\xfc\x1e\x8d\xe1c\xe9\xf3\xd1G\x9f\xd5iw'\xadf}r\xa5\xba_\x89\x1e\xd1\xe0Ǧf\xd3[\xf2\xd2k\xd1\xd3\xc5\xe3c*Ї\xf5\xe5Q\xa0\xf3u\xe7\x14Ou\xa6\xc6|Be9\xb1\xab\xe8\xc5\t\xe8\x94\xda\xf1I\x15\xe3\xd9ƛ\xc4:q\xbf\x02<\r\xf2\x88\xeap\x12q\xe6+\xc1G\xd7\u007fC\xbdu\x12\x8f\xe4\xaao\xa4\x9e;\tx\xb4\xd6;Uŝ&y\xa4\u009b^\xbb\x9d\x04\xed\xea\xba\xf3\x15\xdb\xd7\xeb\xcbz\r\x17y\\\xd5\xccV]_\xe4B'\xd4U\x8f\xa9\xa6\xceR\xec\xc4\xcaiS\x19\x1dY\xf7\xd8zi\xbf\x1e:\x024\xa5J:R\x05\x1d\x818Y\x1bM\xad}\x8e\xc0\x9e1\xbb\x93R2\xf1g\xe3u\xff\xc6ʒ\xcb͐\xf3\xa9\xf21)\x1b\x83\xd2iw͞pt\x9d\xe3^X\x11[\xd2\u007f\x90\x18\tA\xea\xa4\x13\x97V-\xe1\x9d\xdc\x0f\xe0\xbaf\xe8\xa8\xcb\xdd\xffb\x85\xb6\xf5̅\xe8~\x95\xe1\xc0vA\x85\x0f\x9cL<\x10\xa6\x81c\x1f3E\x99\xa2t\xcfߝ\x8b8>\x1e\f鈴\xa6\xfd\xe7\x98\xeb\xcc\xed\xf6D\xff\xb9\xa8\x84\xe5e\xf4\x10\x97Z\xed\xb8K\x8amq\xdf\xd0\xf3\x0f徇X\xed\x1d\xa4\x8f\x0f\xcd\xf9Z\x1e\x84\x02,v*\x9eQ\b`f\x88~\xe6\xbf\t\xcc\xd4\xc2}\xe6C\x9c\xac\xe5!|;x\xe5\xce`,@\x95\xf5\xd7j\x05\x81q\xdf\x15\x9aH\x02`ԺL{\xb8\xde\x19w\xef\xfe\xacP\xefA\xed\\\x154\xb8<3\r\xc7^S\x98J\xb4\x1d\x1eA\x01\xfa/Q\x0f<\xffVc\xc0;\xe9mp\x14\xec\xc1\x1e\x1d\x1cRZm\xb4C\xfa\x99\x02\x99\x91\xa1Q\xa8R5\xb3\xe3\xf20ijR\xbbu\xcf\x1b\xfb\x1c\x1f\xfd\xcc\xfa\x1dg\x89\x80N\x8f\x81&@\xa6vߦe\xecg\xbbm\xcf\x15\v\xcdEC\xc9n`Z7\xed9\xbah\x8f\xe8\x9e=\"*:..J&SJ\x97\xecY\xa2\xa33\xc6G爐N\x8b\x91f@\x1et\xbf\xa6\xf4\xb5&\x15\x99\x92K\x14)E\xa5\xf9\xba\xe6t\xbfjB\x9fjB\xf1cn\xa7\t\xfd\xa8\xc7\xf5\xa1&\xd0\xf0L\xd1ә\xe2\xa7sDP獡f\xa3\xa8Yə\xfc\xfb\xe4\x1cy]M\xfd\xa0r\xbcW\xda\xce9\xfc\xf7\x87\xe3#\x15\xacN\x10\xa4D\x0e\xb2\x1e\x1aA\xca\xf9\xf2\xc1\x8f?\r\xa9x\xb1)\xac\u007f\xffy\x0e\x9f\x87f\xe04\"\xe4\x92\xd6\xf1Y\x04\x0f\x9a\xefp1\x92\x95f\xab,|\xb7\xe3,\\(\xa2\xaa<\x04!\xfa\xfbs`\xf9h\x99\xad\x12\x11\xf5c{\xb8\xf2l۩\xe7G\x10\xb2\x91\xa0\xb1R\xab+\x10\x85\xc4\xef\x0e\x15\xfde\xd7O\xffj\xd7R_\xeeߟ=I\x95^\xc1ui\x9dο\xa1եI\xf0#n\xa5\x92Nju\x96\xa3\x13\xa9p\xe2\xea\f@(\xa5\x9d\xa0ϖ\xfe\x04H\xb4rFg\x19\x9a\xd5#\xaa\xf5S\xb9\xc1M)\xb3\x14\r\x03\xaf\xa6\xde\xff\xb4\xfe\x97\xf5Og\x00\x89A\x1e~/s\xb4N\xe4\xc5\x15\xa82\xcb\xce\x00\x94\xc8\xf1\nl\xb2ô\xccЮ\xf7\x98\xa1\xd1k\xa9\xcfl\x81\t\xcd\xf6htY\\A\xf3\x83\x1f\x140\xf1\xab\xb8\v\xe3\xf9S&\xad\xfb\xa5\xf3\xf9\x93\xb4\x8e\u007f*\xb2҈\xac5\x1f\u007f\xb5R=\x96\x990\xcd\xf73\x00\x9b\xe8\x02\xaf\xe03MU\x88\x04\xd33\x80\xb00\x9ez\x05\"M\x99T\"\xfbj\xa4rh\xaeuV\xe6\x15\x89V\x90\xa2M\x8c,\x1c\x93\xe2\xce\tWZ\xd0[p;l\xcfC\xed7\xab\xd5W\xe1vW\xb0\xb6\xdco]섭~\xf5$\xf2\x00\xc2'w ܬ3R=\x0e\xcd\xf6\x01\xae\x8dV\x80\xdf\v\x83\x96P\x86\x949\xab\x1e\xe1y\x87\n\x9c\x06S*F\xe5\xdfD\xf2T\x16\x03\x88\x14\x98\xac{x\x06L\xba\x1f\xe7p\xb9\xdf!d\xc2:p2G\x10aBx\x16\x96q\xd8j\x03n'\xedh\x1ao`%2؋\xac\xc4\v\x10*\x85\\\x1c\xc0 \xcd\x02\xa5j\xc1\xe3.v\r\xff\xa1\r\x82T[}\x05;\xe7\n{uy\xf9(]\xa5b\x13\x9d祒\xeep\xc9\xdaRnJ\xa7\x8d\xbdLq\x8f٥\x95\x8f+a\x92\x9dt\x98\xb8\xd2\xe0\xa5(\xe4\x8aQW\xacf\xd7y\xfa\x0f\x15G\xed\xbb\x0e\xaeG\xfb\xcd7V\x84\x13\x1c \x8d\xe8\x05\xc6\x0f\xf5\xabh\bM\x9f\x88:\xdfn\xee\xee\xdb\xc2$m\x9f\xfaL\xf7\x96\x845, \x82I\xb5Ű\xa3\xb7F\xe7\f\x13UZh\xa9\x1c\xff\x91d\x12U\x9f\xfc\xb6\xdc\xe4\xd2\x11\xdf\u007f/\xd1:\xe2\xd5\x1a\xae\xd9\xee\x90\x1c\x96\x05\xed\xc0t\r\xb7\n\xaeE\x8eٵ\xb0\xf8\xea\f J\xdb\x15\x116\x8e\x05m\x93\xd9\xef\xec\xa9\xd6\xfa\xa12o#\xfc\xaa\xf6\xf8]\x81Ig\xcb\xd08\xb9\x95\to\f֞\xb5\n\xe8iP߆w-\xff\xc2j\xaa\xff\xb5\x87\x87\xd7eլh\xc9~\xb8\x1ds\xb81c$W\x1e\x1a\xe9\x14\xa5\xfb\xdc\x1d҂-J\x04(3\x98t\xb5^\xac};\x82\tAխGp<\xe2*\xff\x84yAjc\x06\xc5\xfbЍP$\xfa\xa4\xb5;U\x19\xfeJ\xcd\xea\xa0]\xe1H\xb9\xf1t;$\xbe\xede\x1a\xb4\xd7\x11Wa\x92\xb3\xd4\x12+\xef\x94(\xecN;\xb2q\xbatC\xbdz\v\xb8\xbe\xbb\xed\rjq\x9e\xb0b\x1bΌv\x1a\x9e\x85<\xe6\xb4o$\x97\xd7w\xb7\xf0@.\x11V0\xc1[rp\xa5Q\xac\x8e\xbf\xa1H\x0f\xf7\xfaW\x8b\x90\x96\xac\x95*\xbb|1\x02x\x83[\xda\xf4\x06\t\x06\r@ch\x0fXFM\x97n\xcd\x0eG\x8a[Qf.(9i\xe1\xfdO\x90KU:<\xe6;L\xf3\xde\x13\x89\xc1\xf9\xd5\xd8{\xfd\xb3\xf5\x8c\x8c \xe9Ǒ\xa1\x03[\xaa\xd0)\xec\xb9\xdf\x18Ue\x86`\x0f\xd6a\x0e\x9b\x00\xa5\xb6\xd5\xcc\x15\xd6\aY\x16\xc0X\xd8\x1c*܇\xd7M^\xb8\xd8dx\x05Δ\xc3\xd3Nm\xdd!\xda|C\xebd\x12A\x99\xf3>i\xfc\xc8\x01\xc2\x18\xfea\x84(=\n\x90\x91\x17O\xe4h\x06\n\x91\xb7\x90e-\xe2\xceS\x05\xe0\xbf\x14|$\x03\x97\x90ٹ\n\xe6Lb\xc6&TiȴzD\xe3g$W\xe1Yf\x19oi\xcc\xf5\xbe\xe3d\xb5\x1b\xd9\x16\x83\x19\x19Iؖdv\xd6@\xb2?*#RY\x87\"]\x9f\xbf\x16\xf3\xf0{\x92\x95)\xa6u\x983\xa8Kz\x8c\xbb9\x1a\xc4\x01\xa1\x90\x8a43\x85_DtU\xff:B\r\xf65\x85A2\x18 \x95\x87I\xa4!E\xb3\x19Q\xd2Ԥ\xc3|\x04\xcfٝ\xbc\x80j\xc2\x18q\x98\xa0Y\x154/!Y=&\xb8b\x99L\x90\x88U;\\L5&\xcd\xc8\xfa\xfe\x1f\x12l\xa7\xf5S\f\x91\xfe\x9d\xfa5\x8e%$|6\x01\x1b܉\xbd\xd4\xc6\xf6\xa3\x13\xfc\x8eI\xe9Fw\x9bp\x90\xca\xed\x16\r\xc1›\x8e\xbf\xa7\x885mV\xa9\x99i\xc6\x1f\xad\xaba:1\x8f\xa91\xb6\x14v_F\xa1\x02#NV\x8fuC*\xf72-E\xc6jB\xa8įO\xd4\xf8\x8d)\xb7\x19\x818\xc2\xdf+\xa3j\x15ĥ\x8eW\xaa\x15\x92ۗk3f\xb7|;\x063N\x86\x8d`gr̅k\x9a)3\xb4\x01\x15o\xfe\x1a\xbds\xd1p\xca\at\x99\xd8`\x06\x163L\x9c6\xe3\xe4\x89\x11\x02\xdfb\xf5\xe7\be\a4i\xd7ߚU\xa2M#\x87l'\x93\x9d7V$e\f\vR\x8d\x965\x86(\x8a\xec0\xb5h\x88\x91\x8c0ٜ\xd2hZ\x84\xfa\xe8\xc3\x1dS$M\x8b\xd4\xc1M\x9b\xd1\xc6]\xaa\xd7b\xf3F\xf4\x0e\x9aꇄ\xfd\xf6h\xf8\xcb\v;\x91[\xa2]\xc3\xed\x160/\xdc\xe1\x02\xa4\xab\xbe\xc6@%W\xb1\xc1\xe3oƸ\xd3v\xcbm\u007f\xf4\x8b\xef\x96\x17\xe1Z\x8d\xc6߄il\xac\ue0adZİO\xed\x91\x17 \xb75\xc3\xd2\v\x8a!\x1d\xb2/5\x87h\xcbљ\xe5\xdcK\x12(\xd6\xf6R˅Kv7\xf51PĈ\x1e\xad\xfa\x00\xbc_^\xc50̃\b\x90P;\x15|\x82)\r\xe6\xfed\xf4\x9e\xf7G\xf3\x85=\xc0\x0f\x9f?b:G2\x88\x97ԣE}\xe8y:m\x14x\x81Q [\x8bb7\xad\x8e\xf1\xfc\xf9\xf7\x05\bx\u0083\xf7\xac\x06\x83ˡF\xac\x155H\x83|\x18\xcfj\xe4\t\x0f\f*\x9c\xaeG\xc1[\"*\xbe=\xe1!\xb6k\x8f\xa8\x84_8\xd7\xf3ԥ\x0f\xbc\x8a\x98\xadԴ\x9a\xa8a\xef\x80\xd3q\x8b\x85eJ\xa9j\x15\xc5O\\vͰΕ\xd2\x13\x1e\xdeY\xcf>\xda5;Y,\xa0\x00)l\xb0\xc8;\xac\xbaKy\x10\x99L\xeb\xc9x\x9f,\x80x\xab.\xe0\xb3v\xf4\x9f\x9b\xef\xd2\x12\x8a*\x85\x8f\x1a\xedg\xed\xf8˫\x92\xd8/\xe2D\x02\xfb\xc1\xbc-\x957\vD\x97E\xf378\xb0\t%\x11\xad\xd9&-\xdc*\x8a\xcf<}\x96\xb0i\x87\x15r\x1e\xad\xbc\xb4|\x1b\xa3\xb4Z\xb1\x99\xaef[\x00\xb4\x8dW`\x956\x1dN],\x848\x88b@\uf7ac\x95\xff\xe5\xe8\x1ek\xaa\x19,2\x91`Z\x9dJ\xf3\xa5\x99p\xf8(\x13\xc8\xd1<\"\x14d7\xe2\x85j\x81&\xf7\xed\x04)\x8cw-\xaa\x16\xcc\xc2\xc0\x1d\xd0P[Ѯ\x8f\xecY\xb19\xaa\xfb\xc8\r\xd9t\xf7\xb8U\xb2yg\u007f(\x8a\xfa픎e\x96e!\xbf\x8e}\x10\x8f\xa4w?r\xc1\xc7\xd6\u007f\x90ye\xf1\xfe3\xce\x1a\ni\xec\x1a>pBK\x86\xed\xf1\xd5)ak\xaa(\x90\x84\x89\xb4@r\xb2\x17\x19\xb9\x0f\xa4\xbc\x15`\xe6\x9d\t\xbd=\xf2\xa0\xe2T\xcc\xf3N[o\xf3\xebc\xf5\xf3'<\x9c_\x1ci\xaf\xf3[u\x1e\a\x93t\xfe\x91Ҫ\xbd\x16\xad\xb2\x03\x9c\xf3o\xe7\xec\x98-\xd9\"'8o\v\xa4:\xba+\xa7\x8e,\t\x05(֮\xbc\x16\x1a\\'X\x90\v?\xb7\x8ah\x99.\xb4\x1d\xb9]\x1cA뫶\xce\x1f\x00v\xdc\xed\x81\x13\u0098\xe8/\x9c\x1a\x82\xd8:4`\x9d6U2\x03\xa9\xdd\xde\x019q\xde\xce\xf3\x9eX]\x9fFz\xc0\x14d\x9e7\x1a\xc2\xeb\xf4s\x9f\xe5@\xff?\x0f3ag\x89a\x17F'h\xed\xbc(EZ\x8e\x99\x03\xdb\xfa\xb0V\xf8\xe0m\x1b\xa5\x9ac\x8e\x92\xab\xb6\xcc\x15'Ҟ\x10\xd8\xdc|o\x9d;\x93\x1a\xa2\xbfcD\xf9\x14\x1c\x81\x13\x1d\xf3\\\xf4\x13k\xa2ѽ\xf6\xa3\xab\r\x18\x80\xf9\x80\xc9<\x96\xacT\x96\xf9\xcdA$\xffj\x8eG.\xd5-O\x04\xef_\xcdY\x81J\x95㩡\xccu5\xbeaH\xfd!6~\x85*=C\xf3]\x8d\xc1\x0eg\x8fo2\xe29\x05\xe4L+\xedڇ5a\xa6w\x16\xb6\xd2X\xd7 \xbc\x00\xaa\xb4|M\xfd\xba1\xa6\xba1\xe6\xe4\x10\xf3\x8b\x1f\xdd:V\xdc\xe9\xe7\x90Դ$\xb0\xae\x88\xbf\x13{\x04\xb9\x05\xe9\x00U\xa2K\xc5\a^\xa4.h\x9a\x05\x10=\x13\xbd1\x89\xb4\x99\xad\xc1\xaa\xcc\xe3\t\xb2b\xe9\x94j\xf6t\xac=\xe4g!\xe3N\xa7\xe04\xb6\xba\xa9ġ\xa1\xd6͆\n\x19D\xed\xec\xb5\\|\x97y\x99\x83ȉ-K\xe2ƭ\xcf=\xaaR\xdd<\xaf\x9f\x85t!\x83\xd8_\xac.Ӧ\x89\u038b\f\x1dVYE\x89VV\xa6X\xbb\x0f\x81\xff\x839ZcM\xc0VȬ4\vt\xf4b\xce,\x8dۂzz\xf9`,\x1e\x91\x15\x133\xf2\xd0}\x81\xd3\x1f\xa8\xc5\xe7\xa5-\xcdF\xab\xf3\x87\xe6\xad\xca\x0f\xe4\xa0-{ 0\x9bo\x16\x834\xc4d\x99\r\xe7\x8f\xcd@]\x92[\x16\x1b\x83G\xe4\x91\xc5g\x8fő\a\xf8\xb5}l\xceX\xb4\xd7\x16\x9b\x1f\xf6:Ya\x91\xb9`\xad\f\xafY\x90'f\x80E\x13,.\xdb+:ǫ\x95\xb95O\xad\x89̮\xe1|\xadY\x90C\xf9\\1YZQ\xb8F\xe7f\xd5\x19W\xf3'\x89?\x94\x91\xf5\xf2\xb9\xdf/\xe9\xe7O\xe7WEeUE\xc5\x02\xf38G\xe5M-͖\x8a\xa2\xea\xd2̨:\xebib\xe2\xa8|\xa8\xe3\\\xa7\xa9\xa5\xccfA\x8dg8M\x81\x1d\xca}\x8a\xc8k\x9a\x00\xd9\xcexZ\xec\x06\xccJ\xd3L\x87\xe1\x8a\x18U\x9b\xb7\xb5\xd9\xff\x85\x04\xfe袵\xe9\xb8\xc01Qɗ\xde\x10\xe2}\xe5\xf5\r\xb9\xd5\xe31\x9ew\xb6Op\xabG@\xden!/3'\x8b\xacU\x92\xc2\xed\xf0P?y\xffM\xf3\xd3\xcb́\xa1}\xf9V\v\xf0\x18\xc8n\x80 ,\xe8\a]?\xfc\xcc%\xfa\x1a\u007f\xd1]R\xec\x15|\xdc5\xcf\xfc\xf3\x95\xc8g+\x91\x97@1\xd8G>OY\xfe,%\x92\xce'\x06[\x93SG>?Y\x14n\x9d\x18pMB\x9czn2\x1drM\x1f\xa7\xf5\x9f\x99\x9c\xe0NDH\xd8l\x97\x1f\xbe\x11\xd0&E3{\xb9\xb2D4g\x85\xb2\x17\x13u\xe7\xefU\x1d\xa8\xaa{Q\xaf\xf6\xc5\xcd\x18wt\xfd\n>\x81_\xa4J=oH\b[\xfe\x05\x17\xc9\xe4w2\xb5\xe33.F\x8d\xb7ٻ4\xb2X\bR\xa3\x1c\x14\xf1\xe5\xb6]ÍHvu\xc7\x11\x88<\xf3NX\xd8j\x93\v\a\xe7\xf5m\xdce5\x92\xbe\x9c\xaf\x01~\xd6\xf5Eh\xab\xca\xcd\b\\+\xf3\";P\xf4\x03\xe7]@?&:\xa3\xe2gC\xc1\xbfP\xd1,\"\x02\xbe\xeb\x8e\x18*8\x19\n\xbb%\x99.\xd3z\x86\tv\vu\x80\xaf\x0f\xecMq\x11\xa8\xa4)\x96\x15|\xa5*\x12\xee\xd5\xd2\x1a\x019V7r\x11\xc9\xc6/\x87\xad\xd3F<\xe2'\xedK{\xc6Ь;\xa2S\xdd5\xe8\xaa*U$\xbc\xfd\x1a\x95d\xbf\xb6>\xc0&\x83\xec\xa8\xda a;\xa6\xc4f\xf6\xb9sY\xc4\xe2\xee\xef?\xf9\x059\x99\xe3\xfac\xe9/\xeaW\x850\x16\x89\xd2\xd5B\xfd\xa0\u0378\x9d\xdb\xe9g.\xd7\u05ee\xbf٪p\x8c\x9c\xb1\xc69\x01'\xadfߩpY\x91.F\xe4\x1f\x86G\xb6\x02\xd9\x16\x13\xa7\xae\xf6\xf5v\x14\x96\xb0V'\x92u\x11\x1f\tq\xa2\xd8땊\x9b\xb2(\x13*\xa3\xb4\xf8\xe5Y\xa1\xf9VmT{\xab\xc6\nlvH\xf8\xeb\xd1\xc0\xd1\xe2\x9aN\xb3\xfe\xebu\x1f\xb2{*\x10\xc8\xfab\xa4\xd5ٖ\xb4u\t\xdac\xd2\xcd\xec\xff\xf1\xbd?컮\x86\xab\xbe\xae\xeaB\xb4g\x11\x94\xf5\xc5Vcj\f\xfb\xaa\xac\x89(\\i\x82yMJ\xc3u\xf3\b\b\xfa\xb2r\xa7U\x19n\xaa\xb6\xcf\xf0\xb2\xa9\xe3\xdeD\xfb\xb3U\xe3\a\xf8W\xd7\t\x1e-\x9c뭫\xaf\xea\xbe\"\xf8\xa7\xb1sp\x1fp\x9d\xc1\xb9\x9a\xcaԧN\xf2\r\x84\xe6\x81U}»1ԇ\xb36W\xf0\x19\x9f\a\xbe\xde(Z\xc4\xf1\x9d\x9aO\xcdĔ\xcf\b\x86*\xacO.q_\x8f\xe2\xbc\xd8\x01m\xd1Us\xbd\uef44\x1b\xaeV[w\xf19\xb0Cl\xfdG\xb9\xf5\a8\t\xad韎z\x8c*\xaeI\xa55\xa6\xb0\x06\xb7\xd4\xd1G\x8bf\xcf\xe5a+!\t6\xbc\xfd\xa5\xdc4\xe5\"\xe1\x8f?Ϛ])\x92\x04\v\x17\x12\xbb\xda\xff\x9aŹ/\xf7Z\xfdc\x15\xfcg\xa2\x95w\xb4\xed\x15\xfc\xe7\u007f\x9fA0\xc0\x0fտHA\x1f\xff7\x00\x00\xff\xffX\x13X\x17\xfbc\x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4VO\x8f\xeb4\x10\xbf\xe7S\x8c\x1e\x87w!\xe9{\xe2\x00\xca\r\x15\x0e+`\xb5\xda>\xed\x05qp\x9di;\xacc\x9b\xf1\xb8K\xf9\xf4\xc8v\xb2m\x93\x94]\x90\xf0-\xf6\xfc\xf9\xcdo\xfed\xaa\xba\xae+\xe5\xe9\t9\x90\xb3-(O\xf8\xa7\xa0M_\xa1y\xfe.4\xe4V\xc7\xcf\xd53ٮ\x85u\f\xe2\xfaG\f.\xb2\xc6\x1fpG\x96\x84\x9c\xadz\x14\xd5)Qm\x05\xa0\xacu\xa2\xd2uH\x9f\x00\xdaYag\fr\xbdG\xdb<\xc7-n#\x99\x0e9\x1b\x1f]\x1f?5\xdf6\x9f*\x00͘տP\x8fAT\xef[\xb0ј\n\xc0\xaa\x1e[\b\xc8II\x94\xc4\xc0\xf8G\xc4 \xa19\xa2Av\r\xb9*x\xd4\xc9\xf1\x9e]\xf4-\x9c\x1f\x8a\xfe\x00\xaa\x04\xb4ɦ6\xd9\xd4c1\x95_\r\x05\xf9\xe9\x96\xc4\xcf4Hy\x13Y\x99e@Y \x1c\x1c\xcb\xfd\xd9i\r!py!\xbb\x8fF\xf1\xa2r\x05\x10\xb4\xf3\xd8B\xd6\xf5JcW\x01\fLe[\xf5\xc0\xc5\xf1s1\xa7\x0fث\xe2\x04\xc0y\xb4\xdf?\xdc=}\xb3\xb9\xba\x06\xe80h&/\x99\xef\x85Ȁ\x02(\x18P\x808PZc\b\xa0#3Z\x81\x82\x12\xc8\xee\x1c\xf79G\xaf\xa6\x01\xd4\xd6E\x019 d\x05\xd9*\x03Ge\"~\r\xcavЫ\x130&/\x10텽,\x12\x1a\xf8\xc51f2[8\x88\xf8ЮV{\x92\xb1\xeb\xb4\xeb\xfbhIN\xab\xdc@\xb4\x8d\xe28\xac:<\xa2Y\x05\xda\u05ca\xf5\x81\x04\xb5Dƕ\xf2Tg\xe86w^\xd3w_\xf1Ч\xe1\xe3\x15V9\xa5\xca\n\xc2d\xf7\x17\x0f\xb9!\xfe!\x03\xa9\x1dJ}\x14\xd5\x12ř\xe8t\x95\xd8y\xfcq\xf3\x05F\xd79\x19S\xf63\xefg\xc5pNA\"\x8c\xec\x0e\xb9$qǮ\xcf6\xd1vޑ-ե\r\xa1\x9d\xd2\x1f\xe2\xb6'\tc\xed\xa6\\5\xb0Σ\b\xb6\b\xd1wJ\xb0k\xe0\xce\xc2Z\xf5h\xd6*\xe0\xff\x9e\x80\xc4t\xa8\x13\xb1\xefK\xc1\xe5\x14\x9d\n\x17\xd6.\x1e\xc61w#_\vݽ\xf1\xa8S\x06\x13\x89I\x9bv\xa4s{\xc0\xce1\xa8%\x95\xe6]H\xb2ƿ\xc42L\x92\x82f2_R\u007f\xbe\x8dfy\x9c䗃\n8\xbd\x9c`zH2S\xff\x86v\xa8O\xda`1Q\xa6\t\xbe\r%\x1d\xb4\xb1\x9f\xfb\xac\xe1\x1e_\x16n\x1fإɚ\xe7\xfa\xf5\xb9Q\x1bP\xfe7{\xb2\xb3p\xa7\x91\x15\xa9\xfc\x0f\xbb\x1c\xd5\x17\x03z0\x04\x1c\xadM};\x9b\x90\x19\xc8t\x92\xcfdH\xb0_@\xb3\x88\xe7\xce\xee\\\xde\x04Tr\xac\xa4\xf4\x13\x0e\xc9\x1e\xfc\x14\\\v\x06o纜\xf9\xf0z\x17\xa1\xe5\xe4?\xe9\u007fSN\xe3\x86\x18\x17}\xd7\x19\xd5\xe2C\xf2\xb8\xc4\xf8r\u007f\r(\xa31jk\xb0\x05\xe18\xd7.\xba\x8aY\x9d\xa6U3\x96\xday\x9fz\xa3\x80f\n\xa9O^\x0ehou\x03\xbc\xa8锿\xf2\f\xdb\xd3-\xd5\xf5\xebr8o\xa9R\xba-\xa4\xd9]\v-p\xf6.R\x16\xb3WJzq\xf3\x98\x11\xb2\xb9\x94\x1dg\xc6Uk\x8c\x8b\xc8<\x86\x9b\x10\x16\x93=\xbb\xcc滋\xf0\x828V\xfb1\xe0\xf3\xe8M\x9b\x9a\x17\xec\xee\xa7+\xee\x87\x0fW\xbbj\xfe\xd4\xcevT6t\xf8\xf5\xb7\xaaX\xc5\xeei\\0\xd3\xe5\xdf\x01\x00\x00\xff\xff\xfb\xb1p\x12\x1b\f\x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4WO\x8f۶\x13\xbd\xfbS\f\x92\xc3^\"9\xc1\xef\xf0+t)\x82M\x0fA\xf3g\x11o\xf7R\xf4@\x93#\x8b]\x8aTgHm\xddO_\f)\xad\xbd\xb6\x9cl\x8aV\x17C\x149|\xf3\u07bc!\xbd\xaa\xaaj\xa5\x06{\x87\xc46\xf8\x06\xd4`\xf1ψ^\u07b8\xbe\xff\x81k\x1b\xd6\xe3\x9bս\xf5\xa6\x81\xeb\xc41\xf4_\x90C\"\x8dﰵ\xdeF\x1b\xfc\xaaǨ\x8c\x8a\xaaY\x01(\xefCT2\xcc\xf2\n\xa0\x83\x8f\x14\x9cC\xaav\xe8\xeb\xfb\xb4\xc5m\xb2\xce \xe5\xe0\xf3\xd6\xe3\xeb\xfa\xff\xf5\xeb\x15\x80&\xcc\xcbom\x8f\x1cU?4\xe0\x93s+\x00\xafzl`\f.\xf5\xc8^\r܅\xe8\x82.\x9b\xd5#:\xa4P۰\xe2\x01\xb5콣\x90\x86\x06\x0e\x1fJ\x88\tW\xc9\xe9.G\xdbL\xd1>L\xd1\xf2\x04g9\xfe\xfc\x95I\x1f,\xc7\xe8\xcdK\x9a,\xcaWO\xb0ƽT\x11G\xb2~w\xf4!\x1b\xe1+\n\x88\aJ!\x94\xa5%\x8b\x03\xd12$\xec|\xf9is\v\xf3\xd6Y\x8cS\xf63\uf1c5|\x90@\b\xb3\xbeE*\"\xb6\x14\xfa\x1c\x13\xbd\x19\x82\xf51\xbfhgџ\xd2\xcfi\xdb\xdb(\xba\xff\x91\x90\xa3hU\xc3u\xeeB\xb0EH\x83Q\x11M\r\xef=\\\xab\x1eݵb\xfc\xcf\x05\x10\xa6\xb9\x12b\x9f'\xc1q\x03=\x9d\\X;6\xd8\xd4\xde.\xe8\xb5\xec\xe4̀\xfa\x89\x81$\x8am\xed\xe4\xec6\xd0\t\xafj\xf6\xf9r\xbc\xfa\xc9\xf4e\x83C\xe9\xfe\xadݝ\x8e\x02(c\xf2١\xdc\xcdŵ_!l!\xef뼓\x14j\x1bH\x10\x8d\xd6 Us\x9e\x13\x92DS\xc2\x16\x9d\xe1\xfa,\xe4\x05\xces*\x84F4V\xee\x1c\xe8S$\x8f\x13\xf3᧬/\x94\x1f\x02\xe4ң~\xea\xb1>\xa27\xb9\xa9\x9f\xa1\t\xb9\x86\x19\r<\xd8\xd8\x15s\xb8\xe3C\xeay*\xc8s\x8f\xfb\xa5\xe1\x13\xec\xb7\x1d\xca\xcc\xd2N\x11\x185a\x14\x1c\x8cN\xcc+ά\x01>&\xce\xf6R\x8b\x11AZ\x845\xf3\xea{ܟ\x13\r\xdf\x12w:\xef\xbf\r\xf9J\xce\xc5\x190a\x8b\x84>.Z\\\xee\x1e\xe41bv\xb9\t\x9a\xc5\xe0\x1a\x87\xc8\xeb0\"\x8d\x16\x1f\xd6\x0f\x81\xee\xad\xdfUBxU\n\x81\xd7\xf9ް~\x99\u007f.\xa4|\xfb\xf9\xdd\xe7\x06\xde\x1a\x03!vH\xa2Z\x9b\xdc\\hG\xa7ݫ\xdcq_A\xb2\xe6ǫ\u007f\xc2K\x18\x8as\x9e\xc1\xcd&W\xff^N\xee\fJ(\xda\x14U\x02\x81\xf4M\x11\xbb\x9f\xd4,\xfda\xa9\x10gL\xdb\x10\x1c\xaa\xf3ғ\xeek\t\xcd9\xa4Jv\xf8\x1e\x9b\xcd\xce\xfd\x86\xc9n\xa6ibx\xc9j^6\x17B\xb9\x97\xe4[\x8a\xda\xe1%\xa3/p\xbc\x9cJ\xf5\xb8\xc1\xb3ZtT1\xf1\xf77\xe9\xbcl\x9a\xb9\x9d\x1a\xb5N$\x05=\xc5\\\xb8\xd0\xfc;\x8dz\xe8\x14/\xb8\xed\x19\xa8od\xe5,\x83\xb3-\xea\xbdvX\x02Bh\x17\xaa\xe9\xbb ˃>\xf5K\xa5\xf5vT֩\xadÅo\xbfxu\xf1\xebE\xf1\x17\xf5<\x1bd\xb9\xb5\x98\x06\"\xa5\x12{\xaa\xb2i䠾\xd2\xd2\\\xd0|:\xfd\xdb\xf1\xe2œ\u007f\x0e\xf9U\a_\xceDn\xe0\xd7\xdfV%*\x9a\xbb\xf9\xa2/\x83\u007f\a\x00\x00\xff\xff\xe4\xf3S\x85\xb2\r\x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xec=Msܸrw\xfd\x8a.\xe5\xe0\xf7\xaa4\xa3u吔n^\xd9[Q\xedƫ\xb2\xf4\x94C\x92\x03\x86\xec\x99\xc1\n\x04\xf8\x00p\xe4I*\xff=\xd5\x00\xf8\r\x92\x98\xb1\xf4\xde\xee+\xe3b\x8b\x04\x1a@w\xa3\xbf\xd0\xec\xb9X\xadV\x17\xac\xe4O\xa8\rW\xf2\x06X\xc9\xf1\xabEI\x7f\x99\xf5\xf3\xbf\x9a5Wׇ\xf7\x17\xcf\\\xe67p[\x19\xab\x8a/hT\xa53\xfc\x88[.\xb9\xe5J^\x14hY\xce,\xbb\xb9\x00`R*\xcb象?\x012%\xadVB\xa0^\xedP\xae\x9f\xab\rn*.r\xd4\x0ex=\xf5\xe1\x87\xf5\xbf\xac\x7f\xb8\x00\xc84\xbaᏼ@cYQހ\xac\x84\xb8\x00\x90\xac\xc0\x1bذ\xec\xb9*\xcd\xfa\x80\x02\xb5ZsuaJ\xcch\xae\x9dVUy\x03\xed\v?$\xac\xc3\xef\xe1G7\xda=\x10\xdc؟;\x0f\x7f\xe1ƺ\x17\xa5\xa84\x13\xcdL\xee\x99\xe1rW\t\xa6\xeb\xa7\x17\x00&S%\xde\xc0g\x9a\xa2d\x19\xe6\x17\x00a;n\xcaUX\xf0\u1f47\x90\xed\xb1`~-\x00\xaaD\xf9\xe1\xfe\xee\xe9\x9f\x1fz\x8f\x01r4\x99\xe6\xa5uH\xf1\v\x03n\x80\xc1\x93\xdb\x16\xe8\x80~\xb0{fAc\xa9Ѡ\xb4\x06\xec\x1e!c\xa5\xad4\x82\xda\xc2\xcf\xd5\x06\xb5D\x8b\xa6\x01\r\x90\x89\xcaX\xd4`,\xb3\b\xcc\x02\x83Rqi\x81K\xb0\xbc@\xf8Ӈ\xfb;P\x9b\xdf0\xb3\x06\x98́\x19\xa32\xce,\xe6pP\xa2*Џ\xfd\xf3\xba\x81ZjU\xa2\xb6\xbcƳo\x1d\xae\xea<\x1dl\xef\x1da\xc0\xf7\x82\x9c\xd8\t\xfd6\x02\x161\x0fH\xa3\xfd\xd8=7\xedv\x1d\x87\xf4\x00\x03ub2,~\r\x0f\xa8\t\f\x98\xbd\xaaDN\\x@M\b\xcb\xd4N\xf2\xffi`\x1b\xb0\xcaM*\x98\xc5\xc0\x00m\xe3Ң\x96L\xc0\x81\x89\n\xaf\x1cJ\nv\x04\x8d4\vT\xb2\x03\xcfu1k\xf8w\xa5\x11\xb8ܪ\x1b\xd8[[\x9a\x9b\xeb\xeb\x1d\xb7\xf5i\xcaTQT\x92\xdb\xe3\xb5;\x18|SY\xa5\xcdu\x8e\a\x14׆\xefVLg{n1#B^\xb3\x92\xaf\xdcҥ;Q\xeb\"\xff\xa7\x9a\x01̻\xdeZ푘\xd1X\xcd\xe5\xae\xf3\xc2q\xfd\f\x05\xe8\x00x\xfe\xf2C\xfd.ZD\xd3#\xc2ΗO\x0f\x8f]\xde\xe3f\x88}\x87\xf7\x0eC\xb6$ \x84q\xb9E퉸ժp0Q\xe6\x9e\xfb\x1c\xeb\n\x8er\x88~Sm\nn\x89\xee\x7f\xad\xd0\x10\x93\xab5\xdc:\x11\x03\x1b\x84\xaả3\xd7p'\xe1\x96\x15(n\x99\xc17'\x00aڬ\b\xb1i$\xe8J\xc7ag\x8f\xb5\u038bZ\x96M\xd0\xcb\v\x84\x87\x12\xb3ށ\xa1Q|\xcb3w,`\xabt+/\xbc\xb8Z\xf7@Ə,\xb5\xcc\xf0\a\xc9J\xb3W\x96䯪\xec\xb0\xc7`A\xb7\x0fw\x83\x01\xf5b\xc2ҜX\xa9\f\xe6t\xce^\x18\xb7\xb4\xbc\x11L @\xf0\xe4$L\r\xcfI\x9aʀ\xad\xb4t\xa7\xf4\v\xb2\xfc\xf8\xa8\xfeb\x10\xf2\xca1k\xad+\xae`\x83[\xa51\x02W#\x8d\xa7Ψ5!Ƹ%\xa9ʮ\xe1q\x8f\x84FV\t\x1b\xf8\x9e\x1bx\xff\x03\x14\\V\x16\xd7#h\x13\x04\xf6Hq`\xfc\x0ẹ\xfa\xc9xR-\xa0\xef\xe3İ\x0e\x12_\xf6h\xf7\xa8\xa1T\xb5\b\x8e\xecr\xcb\x05\x829\x1a\x8bE\xa0x-\xf86\x01\xfb\x8e)\x84\b \fl\x8e\xf5\x9a\xc7\xfb$}\xcb6\x02o\xc0\xeaj<\x9dG\xc3F)\x81l(\x84\x87x\xf8\x82\xc6\xf2l\x01\v\x97C4\xf8Q\x11$\xe8\xf0\xc2\xed-\x82\x87M\xcbk\x96=#\xb0\x1a\x1b\xa4\x1c\x84\xe8 \xb1\x87\x01\xf8/\t\x1fIre$Oƫ\x85 \xb98\n'-\xa5\x02\xa1\xe4\x0e\xb5\x9f\x8d\xb4\xc2\v\x17\x82\xa6\xd7X\xa8\x03\xe6@\x02C\xa3 \xc9\aۊd\xc9\x18\xcf\x00\xc4˓<\xc0\xa5\xb1\xc8\xf2\xf5\xe5k\x12\b\xbff\xa2\xca1o̖\x91\f\x18\x10\xe7\xd3h\x803\xe9\x18\x97\xa45Ȉ\"\xe4\xca\xf6-\x19&\x91\xad2\x8d@r\x9bK\x0f\xcf\xd9\x1c{\x8c\n(j\xdcb\x11Y\xdb\xec)L\xc4\fӚ\x1d'\xf0R\x9b\xb7\xa9hi\xfa\a-*x\xe6\xec\xafFW:\xccxk\x8dE%\xd4\xef\x18){\xa5\x9e\x97\x10\xf1oԧ\xd5\xfb\x909/\x016\xb8g\a\xaet\xd8z+\x8d\xf0+f\x95\xc5\xd8\xe9e\x16r\xbeݢ&8\xe5\x9e\x194\xde\xf4\x9bFȴ*\x03'\xfc'\x899\xdaGKH\xe2T\xb7\U000e99538\x1a\x9e\xab\xba\xd1BI۸\xf3\x9b\xf3\x03\xcf+&\xdcQf2\xf3\xfbaͺb\"a\x86ȣ5{AQ\xaf\x9c(\xd13\r\x94DP\x1a\n\xb2\x87\xc6]\x87\x16\\ۦ\xb6\xbda$\xed\x94gQ]\t4a*\xaf^Z\x19p5\t\xba\xa1\x88\xb7\xa5\x05۠\x00\x83\x023\xabt\x1c\x1dKD\xf6-E\xaeM`1\"\xe1\xfa6L\xbb\xb1\x19\x90\xe0\f\x9c=\xcf\xf6^Y\x10\a98\x90+4\ue533\xb2\x14ǩM\xc2\x12\xe5\xc3$s\a\xbdm\vG~\b/v\xf8ۖ \x1b۶ %\xfb\x98m\xd8\x01\xac\x9a\xdd\xf6?&bk\xb1\x7f\x06\xd3ލ\x86\xbe.\xd3\x12J9\xb9\xb3w[\xc0\xa2\xb4\xc7+\xe0\xb6~\xba\x04\x91L\xadv\xfe?0aN\xe7\xf8\xbb\xe1\xc8W\xe5\xf8Y\xaa,A$\xaa4\xd3\xff\x01\x89\xe2\x94\xc5C\xd0\x15\xc9\x04\xf9\xa5;\xea\n\xf8\xb6!H~E~\x93E=\xa0\xcc7\x9d\x97\xd7@F\x8a\xbe\xa3V0\x9b\xed?}%\xcb˴\x01\xd7D\xbc\f\a{\xfb\xb5\xb6\xe7\xfb\x8ay\x01.\xb8\x00\r\xd7X\xf8\xc0ϣ\xc3f\xfb\xc4YT\x1f>\x7f\xc4|\x0e=\x90\xc6y\xa3\x8d|\x18,\xb6;u0\xcaS\xb7\x11L\x9fƿ\xf1!\xbd+`\xf0\x8cGo\xb10\tD\x1cF\x13Mx:c\xe4\xb8آc\xb2g<:0!X\xb88:\x95\x15|{\xc6cJ\xb7\x01\x02iM܄ (a\x92\x1e8D\xb8\xd8R:\xf2\xc0\x05~kY\xb4\xbc9H\x17$u\xabq\x7f\xc66\x1b\xb2u\x82掰\xef\x8c'\x11\x9d\x82=/\x137Jj\x0e\f\xba\xd3R\x87~\x9f\x98\xe0y3\x91\xe7\xfb;9m\r\xf7\xdbge\xef\xe4\x15|\xfa\xcaM\x88\xbe\x7fTh>+랼\t:\xfd\xc2\xcf@\xa6\x1f莗\xf4b\x9b\xf0Ѝ!'0\xb7ow\xde\xc3k\xc8\xc3\r\xdcI\xf2[\x02>܍\x80\x9fn^?\xf4[Q\x19\x17$\x96J\xae\x9c\xaa\\\xc7f\xf2\xc8N\x04\xa9t\x8f\"\xe3\xa55\x93\xfa\t\x13\xc1>\x92&\xf1\xe3\xfd\x1d\x87`\x19\xe6u\x8c\xd3E\xe6\x99\xc5\x1dϠ@\xbd\x9bS\x1c\xddV\x92|O[B\xa2\xd4\xf5\xedD\x0eKS\xedu\v\xa2;\x1a\x82\xeb\xb7\x15\x9d܄^5\xb1\x17\xbbN\x04䧻.\xefȩXg\x7f,b\x97幻Ke\xe2\xfe\x04\x89\x7f\x02-ƺ\xdf/\xcckȂ\xb9\x10\xe9\xff\x92\x9as\f\xfd\x7fP2\xae\x13\xce\xf0\aw5*\xb076D\xb1\xba\xd3\xd0\f\xdc\x00\xd1\xf7\xc0\xc4\xf8\xaa'\xb29E\xb2\x05\x85W\xe4j;\xb2X\xae\xe0e\xaf\x8cש.4\xbb\b\x92\x1b\xb8|\xc6\xe3\xe5\xd5H\x0e\\\xde\xc9K\xaf\xe0O\x167\x8d\xb5\xa0\xa48¥\x1b{\xf9-FP\"'&usWЩ\xa62\xf9\x92\xb5%@\x03\x9b{W2s\xe7V\x9dć\xa52\x91ۤ\x89\xa5\xdc+c}d\xb1g\x96\x9e\x12\xc5\x02\xcfC!z\x05l\xebo\xbe\x95\xae\xef4I\xec\r\x02\xaeD53/a\x89\x8cMD\xcc\x03%\xc7\xea\xb2=\xc1^\x9e^\xfa\x8bN7\t˜q\xb1\b\xb7\xd4*Cc\xe6Y$AZ/\x04\t\x9b\x00!\xf3\x0e\x8c\xbf0\x9c\x0fJ\xd6-\xdd %$\x9dh\xca\x7f\xfaډ^\xd2᧿\x97\x98\xef\xd4u\x81;\xb3E\xc1\x867\xe3IK\xbc\xf5#\xebc\x12\x00y\xd7@\xef*w\xd4\xd3-\xc8\xc0H\xbf\a5]py\xe7&\x80\xf7\xaf\xae\xd6\x1b!\x89\xe7\x18\xee\xb7\xf5\xd8\x16\xe9\xcd\x03wzS-\"\xe5\"\xf7\x1a{\x94\x1bǹ\xc9PL\x04)\x95\xed\x86\x13\bn\xa9\xf2w\x06\xb6\\\x1b\xdb]h*ST\v\xa7\xbfm\xa7zN\xf2\x93\xd6g9N\xbf\xfa\x91\x9d@\xd6^\xbd\xd4\xf9\x05\x93W\xb1\xb1\xe6.\x85\x10\xf8\x16\xb8\x05\x94\x99\xaa\xa4\v\xbf\xd0QwSx\x12x\x01\x9d\x8c\xb24\x01A\reU\xa4!`帎\xcb\xd98M\xb7\xfbO\x8c\x8b\xb7 \x9b\x9dJÈ\xb5\x1e\xd9\xea|\x8cn\xa2H\xc1\xbe\xf2\xa2*\x80\x15\x84\xfaT\xb7g\xeb\xb38z\x14or9\x1c\\\xa7F\xac\xa2CU\n\xb4\xa9'\xd2gm\xd011<\xc7F1\a.P\x12\x18l\x19\x17\x13\x97\xe7\xe3v\x12nO\xf15\x82\xb0x='\"m\xf2\x95CEB 6\xd1X\x9c\x97֥N7\x15\xef5\xa6\x99gKA\xe9\xda<+5'^R\xafm\xa1\x05\x16c\xf2\xf8\xddD\x1b\xb5\xef&\xdaB\xfbn\xa2M\xb6\xef&\xdar\xfbn\xa2\x85\xf6\xddD\xab\xdbw\x13\xed\xbb\x896\xd7mNZ/\xad\xc8\x7fq2\xf1rq\x15\t\xd7\xd3sK\x9c\x81\x1f\xb2)n\xfd\xd7'\xa9\x19\x96w\xf1Q\x91\xac\xe0\xf0Y\xcb\xca}\x91\x13\xe3\x806\xe9\xa2U%M\xca%\x1d\x90\x9a\xbd}\x02\xfdB\x12\xe67dߦ$\xfc,\xa5\xf9\xf4\xf3L\x9b4\x9b:\xd1TՓD\xf0P\x7f\xd9Cfo7\x87\xa4\x9f\xaf\xe3\xec\xdcz\xa5\x7f\xf7\x1cԄT\x9c\x85\x04\x9c\xf9\xc4\xdc9|\r\\\x8f>\xc2t/a\xf4w\x83\xaf\x85,\x99\xe9ܘp\x13\x84\x96\x1dޯ\xfbo\xac\n\x992\xf0\xc2\xed>\xb2\x95\x97=Jw\x87%wݴך\xdf\xc2'VC<\x82\xd2 \xb9p\xe8\x9c\xe1\xd6\x1ez\xe1\xd7һp'\x9f\xcby\xf7#-\x97\xe6\xec\f\x9a~\x86̄\x88>\xf5\xca(=Q8=Gf>\xa9\xe5\x94̘a\xde\xcb$\xd0\xe5|\x98\x14\xcfq!\xf7匌\x97\xc4l\xc7o\xbe\x18K\xc9i9+\x93e1!01\x7f\xa5\x9f\x992\x0f\U00084b15$\xe4,g\xa8\x9c\x9c\x97\x12\xf2@f\xf7\x91\x9c\x8d\x12\xc93\x99\x05<\x99\x832\x97]\xb2\x10\x95\x1ag\x9e\xa4\xe7\x94̂v\xf9&˙$\xaf\x97/\xfa\x1a6\xf0\xb4\xa8Y\xcc\x06Y\xb4\x91\xe7\u05f7\x98\xefqJ\x96\xc7\"\xc6\xce\xcc\xe8h26&\xe6=5\x8f\xa3\x9f\xa71\x014%{c\";c\x02\xe2l\xceFjN\xc6\x04\xec\x05\xb5;\xcb%3/\xe3\x1f\x02â~\x13\x7f+\x8e:wcJ\xf7\xcc\xc5%\v\xfd\xd7Aw\xa2em5͛\x9f1˓\xdb\xfd\xe9\xe6gQ\t\xcbK\xe1\xc2\xf9\a\x9eG\x9dF\xbb\xc7c\xf3Y\xe7o\xca}\xe6\xb49:H\xbf~i\xd8s=0\xa2\x99\x81\x17\x14\x02X\x8c\xb9F;\xcf\xfc\xb7\xec\x99Z!\xc9|:p\xe1\x83\xd5\xf0\xc9\xfb\x95\xe7`\xf7%W,\xe2i\xf7X\x10\x94\xfa\xcb\xd7\x13\u070fy\x03\xd1۲\xee\xd9_+\xd4GP\aԭŰ\xf0\x1d\x81?h\xa6\x12m\xe2V\x90\x1f\xbe\x82\xc2\xc0pn\x0f\x1c|\x90^\x85E\xc1\x0e\xd6\xe8\xe0Й\x17\r\xadI\xbc\x91\x1f0\xd15\x1e\xf8P\xcd\xe8\xc8\xfb%\xdb35\t\xffm]\x87ӝ\x87E\xb5\xfd&\x0e\xc4\xf9.\xc4\f\xc8Ԥ\xfa\xb4\v\xa8\xc5$\xfa\xb7r%\x96\x9c\x89d+*-I\xfe-\x92\xe3OH\x8a?\xc1\xa98ͭHFSJ\xf2\xfb\x9b8\x17o\xe8^\xbc\x85\x83q\x9e\x8b\xb1\x00r\x90Ԟ\x92\xae\x9et\xb9\x9a|\xbf\x90r9\xba|\x050\x9f\x86\x9e\x90~\x9ep9\xb0\xb4҄4\xf3\xd3\xd2\xcb\x13p\xf8F\xce\xc7\x1b\xb9\x1foြ\xad\v\xb2\xe8\x84,r\xce\xec볣\xcbJ\xe7\xa8g\x83\xf1\xa9\xac6\xcbd\x03\x7f\xa1?\xe7\xe0\x8bں\xc2\v\xf5Ꙧ\xb1\x90r\xf3\xf5g\x06?s\x99{z\x10Su\xf4x\uf1a05,\xe2\t\x02\xad\xd5\x16*`\xf9k\x05\x83%Ӯl\xda\xe6\xe8\xaf&\xcd\x1a>\xb1l?\x80\xbe\x8f\xfa\t[\xa5\vfᲹ\x93\xb9\xf6\xc0\xe9\xef\xcb5\xc0O\xaa\xb9\xf4\xeaVT0\xbc(ő\xfc\x80\b\xcc\xcb.\x88\xf3\x18\"\xcaL&\x94k\n\xf5k\x16|\xbf\x87~\xef\xc8e^]\xba'\x13\xaa\xca\x1b\xe8\x13\xc4d\xf2\b\xf7O\xceFqeC\xb2\xb6\x84J\xb0Bj\x1fpXa\xe5\xc7\u05ff\xdc3Vi\xb6\xc3_\x94\xafõ\x84\x89~\xef^\x11\xb6 ;\xea\xcb\xf6\xfa[\x8c\x98N\r\x15\xc1\x06\xc0\xda\x1c\x9aQ5(ZeL\xa8̜Gk\xc5\xc2f\x1e\x1f\x7f\xf1\x1b\xb0\xbc\xc0\xf5\xc7\xca_\xa4\xaeJ\xa6\r\x126\xeb\x8d\xf9A\x1b\xfa\xef^\xbd\xc4b\x1d*\xec\xf9\xc7\xe1\xba5\xba<\x1dw_{\xd2\xea\x0f\xbd\xaab5\x8a\x96\x18\xf5)>\xaa\xe3\xa8u\x88\xe4\xcf|\xd4A\x9f\x82\xd3)\xac\xe8B\x18\xee;\x9b\xd7-\xfb3%ŧJϹrk\xcb\xc5\xe7|U\xb6Pj2d{U\xda\xd5\xec\t\x15\xdb\\\x8d\x9b\xf3\xea\xcf\xf9\xe4\x94^\xf9\xcfy:ݎG\xb8\"\x8f:\xefԟkʀ\xbd0\xd3$\xc0D\x15k\vΏt\x96-A\xc3\x1c\xf0\x80\x12\x94t\xf9.\xae\x1a\x8e/D:\x1c\x13\x81څ\x12\x12j\xaaR(\x96\xd7'\xbc\xd6a\xa1x壓_\xfa\x80\xfa\x9d\x99\x81\xd9\x14v\x8b a,0\xbdR\xb9\x81\x9cY\\E\x81&ɾ(\xb3e\x86\xf7\x19\xdd|\xb0\x96\xfc\x84\x98\xed<, 85\xb2\xd6\xc7VY&@V\xc5\xc6+xVw\x88\xd1oTFЄ\f\xa8\x99\xe3\xe57ƥ\xc5\xdd(\xc68\xde\xd9m\xcd?'\xef\xac\x199\xb53Se\x19\x1a\xb3\xad\x84\x88\x99\xfa\r\xe7\xbe\xfe6]n\xdfb\xcd3\xd7ɋ@\x97\x18X\x97\xd5\xf3\x99\x81\x05\x1a\xc3vu\xb1\xb3\x17\xd2@;\x94\xe8̠X\xfc\xd1;\x8am&Y\xbfԗ\x8fh\xb1\xccV,LPg\x02tz\xbd\x8b\xd9\x05B\xed|AD^\x97\x83\xadU\xf3\x898\xf9Zr\x9d\xa2\xca?5\x1d\t7.\x18\xed\bі\xefE\xc1w\x9c\xf4 \x11i\xc7\xf4\x86\xedp\x95)!Х\x9d\x8f\xd7\xf5\x96\x875\xe4\xeb}Af\x16\xb7\xf6S\xb7o\x88|xj\xfbJ\x19̗[t\xd5\\-\xd7ؖG\x1e-H\xb9\x89OR\xdd\x1e\v\xd1B\xc2\xe3\x95v\xfb\xd6\a,\xc8\xd5`O\x87\xba\xc2W\xc1\x18\x8c{\xb7\x05\xfbM\xe9+(\xb8\xa4\x7f\xc8\xfaw\xa1\x89z\xf0I\xebw5\xec\x16\xd6}O}\x9a\xb4\xe9\x8e\"\xc5\xfa@L\x99\xaa\xf1T\xd9\x15|Ʊe\xe5\xb3_1w\xc1\xb8X\xf5d\xear'\xef\xb5ڑ\x7f\x1cy\xd9\b\xafȻ{\xa6-gB\x1c\xfd$\x93\xb3G^|DR\\\x93\xd6K\x1c\xada\x95K\x98\r\xddZW\x9cK\xcf\t.ou\xa3*\xdb\x13%\xad(\x8a_\x038`k\xf8\xac,\xd6\x11^އI\xc2\x17\x8d]\xe1v\xab\xb4\xf5\x9e\xffj\x05|\x1b\xac\xa1\x98\xabǸp7T\xbe\x981p\xdb^ҷ\xdc\xeb\x1c\x1d\xed\x0e\xa1\xab\xf8T\xb0\xa3\xcfadYF\xc66^\x1b\xcbDD\xbe}SN\x943;\x89\xfb0\xffK\xc4\x0e\x1b!\xfc\xaeۿ\xf9\x90\xbc\xd1n\x0e\x9cǜ\xcb1\xf7\xb2=\xaa\xe9\xc0e\x1e\xa3\x84\x17ͭ%yڽ\xc2\x03K\x12T\b0$S&\xca\x06\xceIv\xf7\x9et\xef\xddtH\xb1\xef\xdf4\x9d\xa7Tw\u061c\"\xb2l\x1c\n&\xb6\xe5\xbfa\xe2\xa6\x1eK\xa4\xcc\xf6L\ue229\xb4\xaav\xfb\x9a/'4\xe3TD\xae\xa2EA)\xaa\x1d\xb1z\xb8>\xb1\x95\x96\x9d\x90L\xb8P\xc9;\xcbe\xd9\xf3\xe4JC\x88\xb8.\xa8\x7f\x1d\n\x01\xae\xb6Z\x15\xab@\vw\xebq\x15\xc2$\x9a+\xb2\xffɑ\x9f\x00\xdaV\xdcrlP\x96(\x81\x99\xb0\x9e\x84\x0f\xac\xe6\xc9:\x17\xa7\xb0L\xdbT\xaf\xe2\xa1\xd7y\xc1\xa1p\x90\xe3\xeb}\ba \xff\xa1\xd9\xed\xf0\xa7\r\xae\xc0pY\xd7\xf2\xf7A&\xcf\n\x86\xfc\f\x8d\xceW\x8f^h\x8d<\x84\x9e?\xd0_\xfe\xdf\xd6\x1584\x1a\xe6S\x8aM\xf94\xe8>\xc8\xd6u%\xab\x9b.\xc1\x0e\x8c\xe0\xe3O|\xeb\xef\xd82Z\xf5\x9f\xff\xeeY\xb8\x87$\x9b\xe5ݬ\xb9\xe2,\x91\xc6\xeeX(P}/\x90\xec\b\x83ط\x84ޝd\xf2\x1e\xces\xe2^Ӄ\xab\x7ff\xe2u\xfc\x9a\xc3y\xbeۛ9n\xaf\xbb\xbb\x17\xe6J\xdb/\x9d\xb1\xff\b\xdd\"\x9e[\x80\x10\xf1\xdd\"\xdbh\xbc\xb9E߭\xe3\xba\xd5k\x9c\xa8~=p\xe7^\xc9y\x8b\xea\x81\xd1C'@\xf3\xce\xd9\x0e3\x85'm@\x8ce\x19\x12\xbb~\x1e\xfe\x9c̥\xaf\xcd^\xffb\x8c\xfb3Sҫ[s\x03\xff\xf9\xdf\x17\x10\"\xaeO\xf5O\xc3\xd0\xc3\xff\x0f\x00\x00\xff\xff\xe8ٕ\xadzg\x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4YKo#\xb9\x11\xbe\xebW\x14f\x0f\xbe\x8cZ\xb3\xc9!\x81.\x81F\x93\x00\x83x\xd6\xc6\xc8q\x0eI\x80\xa5Ȓ\xc45\x9b\xec\xf0!\xad\x12\xe4\xbf\aŇ\xba\xd5ݲ\xe4A\xb2ˋ->\x8aU_\xbdٓ\xe9t:a\x8d|F\xeb\xa4\xd1s`\x8dğ=j\xfa媗\u07fbJ\x9a\xd9\xfe\xfbɋ\xd4b\x0e\xcb༩\xbf\xa23\xc1r\xfc\x84\x1b\xa9\xa5\x97FOj\xf4L0\xcf\xe6\x13\x00\xa6\xb5\xf1\x8c\xa6\x1d\xfd\x04\xe0F{k\x94B;ݢ\xae^\xc2\x1a\xd7A*\x816\x12/W\xef?T\xbf\xab>L\x00\xb8\xc5x\xfcI\xd6\xe8<\xab\x9b9\xe8\xa0\xd4\x04@\xb3\x1a\xe7\xb0f\xfc%4\xce\x1b˶\xa8\fOwU{ThM%\xcd\xc45\xc8\xe9\xea\xad5\xa1\x99C\xbb\x90(d\xb6\x92H\x1f#\xb1U\"v\x9f\x89\xc5u%\x9d\xff\xf3\xe5=\xf7\xd2\xf9\xb8\xafQ\xc12u\x89\xad\xb8\xc5\xed\x8c\xf5?\xb4WOa\xedTZ\x91z\x1b\x14\xb3\x17\x8eO\x00\x1c7\r\xce!\x9en\x18G1\x01ȘEjS`BD-0\xf5h\xa5\xf6h\x97F\x85Z\x9f\xee\x12踕\x8d\x8f('Y \v\x03E\x1ap\x9e\xf9\xe0\xc0\x05\xbe\x03\xe6`\xb1gR\xb1\xb5\xc2\xd9_4+\xffGz\x00?9\xa3\x1f\x99\xdf͡J\xa7\xaaf\xc7\\YM:z\xec\xcc\xf8#\t༕z;\xc6\xd2=s\xfe\x99))NZ\a\xe9\xc0\xef\x10\x14s\x1e\x8c\xb6e,\x1e?\x97ț\x1c(\xfb[ƪ\x82E\xf6\\\xb3\x81\x0f \xa4\xa3\x02\xc0E\xa2C\xb0\xa8<\xa3\xf59x\x1b\xde$>7z#\xb7C\xa1\xbb5\xcd%\x8b\xb9B\xba\x87\xdc2\xdeD\xa1\x89\xac\xa3\xb1f/\x05\xda)\xf9\x87\xdcH\x9e9\t6e\xae\x8dD%\xdcP\xd2\v^\x16E\xb1(ȫ\x99\xba\xa2\xc3\xe5ic,\x8d\x99\xd4ɂ[\x021\xd8\xd8:\xa7T\xedQ\x8bS5rƍ\x89Qˡ\x80\x83\xf4\xbb\x14\x0e\u0558\xdf\xc1\xab\xbeG\xe3\x05\x8fc\xd3=ޟvH;S\x02Ep\xc8-\xfahm\xa8\xc8|Ȕ*\x80/\xc1ŀڏ\x13e\xc4B\xad\x9c~\xc1\xe3\x10h\xb8\xa6\xdc\\\xc2\\g\xf9\x8eJ\xe7°\xc5\rZ\xd4~4\xa8Sgb5z\x8cq]\x18\xee(\xa4sl\xbc\x9b\x99=ڽ\xc4\xc3\xec`\xec\x8b\xd4\xdb)\x01>\xcd\x1e4\x8bm\xc5\xec\xbb\xf8\xe7\x82\xc8O\x0f\x9f\x1e\xe6\xb0\x10\x02\x8cߡ%\xadm\x82*\x86֩o\xde\xc7\x1c\xfb\x1e\x82\x14\x7f\xb8\xfb\x16\\L\x93<\xe7\x06lV\xd1\xfa\x8fT\xa8E\xa6\b\xa2UҊ\xb1@\x99\x92\x94]gm\xa6X3f\x88c\x15fwP`\xa2\f2\x16Q_p\x18L_q\xb3\\\xec^\xf1\xb1RHK-$\xa7B\xec\xdc7J\x83!\xce\xea\xed\x11\xc1\xfa\x15\xf8\xa5\x880.x\x12 \xe7\xc3+\x1c?t\xf7\xb6mY\nO9\xc79\xf4T@9\xd0H9\x90\xd9!r1(p\xa35y\xa37\xc0N\xa1\xee\xce\xf5c\xfc\x1b#\xc4:\xf0\x17\x1c\x01~ \xcaǸ\xb1`\x9c\x8e\x11/\xc1a\f\xbe\xd7\u0600\xeb6\xce\xd9\x12\xed-\xbc,\x17\xb4\xf1\x94&\x19,\x17\xb0\x0eZ(,\x1c\x1dv\xa8\xa9C\x90\x9b\xe3\xf8]4\x9e\xeeW\x05\xd5Xa\xe4\x1a\xbf`;.C\x8a\xe1sX\x1fGj\x82\x1b\x84l,n\xe4\xcf7\b\xf9\x187\x16\xc0\x1b\xe6w \xb5\x93\x02\x81\x8d\xc0\x9f\x8a\xb5\v\x82\x9e\xf2\xffC\x8e\"ߠ\x9e\u05fc=\xb1\xf3\x16\x87/\x18_\xf1\x9fǼ\xed\x84B\xf9\x9d#\xffy-xɏG%ڟ\x1e\f\xfe\x94*,>\x92*Ϙy\x1e\x9ex\xa5R+\xcf\x16c\xceLu\x81\xb1\x16]c\xb4\xa0\xe6\xe9\xb6:\xade\xf9\x7fW\xad\x8d\xabuz\x1e\xe5zkE\v7\xb5*\xf1\x89\xe6\xcd\xcdJz\xb8\xea\xb6\x02f\xed\xa8Sl\xfb\x95\x9e\x8c\xbfH\x9b\xf2\xaeӧP?\xac!\xe8X\xa9Ō_\xc1\xdf5|\xa2ޖ\xb2\x93\x98\x13\xdfv\xcc\x00\xa4\x03m\x0et\xbcC/\x92\x00\xa3S\xbe\xa6n\x8di\x91\x9b\xe1\xb8t\x90JQƶX\x9b\xfdhƦBӢ:\x02sd:\xfb\xdfT\x1f\xaaw\xbfZ\x17\xa4\x98\xf3\xd4Ԡ\xf8\x8a{9|\xe5\x19\xa2{?8Q\x1c\xff\xe4\x0e\xf4\xe3\xc7\xd2,\xcfl\xde\xf6\xe3\b\x18\x1b\xa9\xa8\x16\x1c\x89\x13m\xc50|\x8f\xfc\xb8\xba\xbfs\xb1\x84G\xed\xc7ʾ\x03Z\x8c\x1d\x13\n\xaa\xe2M~\x97\bΣ\x1d1\x80\x93\xf6\xa2\xceA\x19\xbd\xed9N\x1a\xf9\x95\x82*\xb4dPƂ@O\xa9Io\x81\xef\x98\xdeb\xfb\n\x95\xf9\x7f\x9dS2\x9f\x9eʹ\x16\"\xf5%\xf3\xb8I\xa3Or\xacL\x1f\xbc\x00\xb7\x9b\xc7_\x7f\v\xf7E\xb3\x17ۜ+\xb8\x0f\xf6\x97,M\xa0N}\xfb\"\u070eooo\x87\xcf\xcd7 \xf1ַ\xf0W\xde5\xe0\xc0\\\xfb*\xfe\xeb\xe1PS\xb5z\xb5\x04\xfe\x92v\xa5\xe7\xc3|\x04\xd8\xda\x04\xff\x9agލ\x19t~\xee\x7f\v\x8f\xf1#Ƶ\"\x83\xf6\x14\x8d\xf0`\xa9\x95l_\xc5bP\x18\xcb-\xb7?/-z\xdfZ\xbak\xc3/17\xc85\x9ak\a\x93)_v\xf4\x9aA\xee΄\xf5\xe9\xa5x\x0e\xff\xfeϤMה\x13\x1b\x8f\xe2\x87\xfeǵw)d\x94/d\xf1'\xa7:&}\x1d\x84\xbf\xfdc\x92\xaeB\xf1\\>i\xd1\xe4\x7f\x03\x00\x00\xff\xff\x1d\r\x93\v\x97\x1c\x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4\x96Ms\xe36\x0f\xc7\xef\xfa\x14\x98}\x0e{y$\xefN\x0f\xed\xe8\xd6\xcd\xee!\xd36\xe3I2\xb9tz\xa0I\xd8\xe2F\"Y\x00t\xeav\xfa\xdd;$%\xbf\xc8v6=\x947\x91 \xf0\xe7\x0f\x04Ī\xae\xebJ\x05\xfb\x84\xc4ֻ\x16T\xb0\xf8\x87\xa0K_\xdc<\xff\xc0\x8d\xf5\x8b\xed\xc7\xea\xd9:\xd3\xc2Md\xf1\xc3=\xb2\x8f\xa4\xf13\xae\xad\xb3b\xbd\xab\x06\x14e\x94\xa8\xb6\x02P\xceyQi\x9a\xd3'\x80\xf6N\xc8\xf7=R\xbdA\xd7<\xc7\x15\xae\xa2\xed\rRv>\x85\xde~h\xbeo>T\x00\x9a0o\x7f\xb4\x03\xb2\xa8!\xb4\xe0b\xdfW\x00N\r\u0602\xc1\x1e\x05WJ?\xc7@\xf8{D\x16n\xb6\xd8#\xf9\xc6\xfa\x8a\x03\xea\x14xC>\x86\x16\x0e\ve\xff(\xaa\x1c\xe8sv\xf5)\xbb\xba/\xae\xf2joY~\xbaf\xf1\xb3\x1d\xadB\x1fI\xf5\x97\x05e\x03\xb6n\x13{E\x17M*\x00\xd6>`\vwIVP\x1aM\x050\xf2\xc82kP\xc6dª_\x92u\x82t\xe3\xfb8Ldk0Țl\x90L\xf0\xb1\xc3|D\xf0k\x90\x0e\xa1\x84\x03\xf1\xb0\xc2Q\x81\xc9\xfb\x00\xbe\xb2wK%]\vM\xe2\xd5\x14\xd3$d4(\xa8?ͧe\x97\x04\xb3\x90u\x9bk\x12X\x94D\x9eD\xe4\xb8\xd6;\xa0#\xbe\xa7\x02\xb2}\x13:ŧ\xd1\x1f\xf2µ\xc8\xc5f\xfb\xb1\x90\xd6\x1d\x0e\xaa\x1dm}@\xf7\xe3\xf2\xf6黇\x93i8\xd5z!\xb5`\x19Ԥ4\x81+\xd4\xc0;\x04O0x\x9a\xa8r\xb3w\x1a\xc8\a$\xb1\xd3\xd5*㨪\x8efg\x12\xde'\x95\xc5\nL*'\xe4\fm\xbc\x04hƃ\x15\x98\x96\x810\x102\xbaR`'\x8e!\x19)\a~\xf5\x15\xb54\xf0\x80\x94\xdc\x00w>\xf6&U\xe1\x16I\x80P\xfb\x8d\xb3\x7f\xee}s:g\n\xda+9\xe4g\x1a\xf9\xd29\xd5\xc3V\xf5\x11\xff\x0f\xca\x19\x18\xd4\x0e\bS\x14\x88\xee\xc8_6\xe1\x06~I\x98\xac[\xfb\x16:\x91\xc0\xedb\xb1\xb12u\x13\xed\x87!:+\xbbEn\fv\x15\xc5\x13/\fn\xb1_\xb0\xddԊtg\x05\xb5D\u0085\n\xb6\xce\xd2]\xee(\xcd`\xfeGc\xff\xe1\xf7'Z\xcf.H\x19\xb9\xd0_\xc9@*\xf3\x92\xf6\xb2\xb5\x9c\xe2\x00:M%:\xf7_\x1e\x1ea\n\x9d\x931\xa7\x9f\xb9\x1f6\xf2!\x05\t\x98uk\xa4\x92\xc45\xf9!\xfbDg\x82\xb7N\xf2\x87\xee-\xba9~\x8e\xab\xc1\nOW2媁\x9b\xdcbSQ\xc7`\x94\xa0i\xe0\xd6\xc1\x8d\x1a\xb0\xbfQ\x8c\xffy\x02\x12i\xae\x13ط\xa5\xe0\xf8\xef07.Ԏ\x16\xa6\xf6}%_\x17\x8a\xf6!\xa0N\x19L\x10\xd3n\xbb\xb6:\x97\a\xac=\xc1Kgu7\x15\xed\x8c\xee\xbe\xc0\x9b\x93\x85\xcb\x05\x9dơM\xceW\xae\x1e\x1er\xee,\xe1\xec\x16\xd6p\xd6s_璛\xe1\xbf$S:\xf1\xc8FG\"trԟեMoe\x81D\x9e\xcefg\xa2\xbed\xa3\xfc\x04P\xd61(\xb7\x1b7\x82tJ\xe0\x05)\x95\x81\xf61\xf5\x194`\xe2\x19\xbf\x11\xcb\xf1\xbf$\x90\xd7\xc8ܜ\xd9Y\xc1ႦW\xb2\x93Fz^\xa8U\x8f-\bE\xbc\x92YE\xa4v\xb3\xb5\xfc\xcf\xfa\x06\x82e\xb2\xb9\x94\x83\xfd\x7f\xfa\x9bIȸ]\x1c\xce#\xd5p\x87/\x17foݒ\xfc\x86\x90\xe7W>-.\v\xbd\xfdc\xe0\r\x94.^ʳIN\xfd\xce\x1cQd\xf1\xa46\xc7\\9\xae\xf6\xfd\xbb\x85\xbf\xfe\xae\x0e\xf7Zi\x8dA\xd0\xdc\xcd_i\xefޝ<\xb7\xf2\xa7\xf6\xae\xbc\x8c\xb8\x85_\x7f\xabJ(4O\xd3\xeb)M\xfe\x13\x00\x00\xff\xff--\nM\xde\n\x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4WM\x8f\xdb6\x10\xbd\xfbW\f\xd2CZ \x92\x13\xf4\xd0·v\x93âi\x10\xd8\xe9^\x8a\x1ehj,\xb1K\x91,g\xe8\xcd\xf6\xd7\x17CJ\xfe\x90\xe5\xdd͡\xbc\x89\x1c\x0e\x1f\x1f\xdf.\xbd\x7f[\xffT\xbf]\x00\xe8\x88y\xfa\x17\xd3#\xb1\xea\xc3\n\\\xb2v\x01\xe0T\x8f+h\xfc\x83\xb3^5\x11\xffIHL\xf5\x1e-F_\x1b\xbf\xa0\x80Z\x16m\xa3Oa\x05ǁ2w\x00T6\xf3~H\xb3.i\xf2\x885Ŀ͍~4CD\xb0)*{\t\"\x0f\x92qm\xb2*^\f/\x00H\xfb\x80+\xf8$0\x82\xd2\xd8,\x00\x86\xbdgXհ\xbb\xfd\xbb\x92Jwث\x82\x17\xc0\at\xbf|\xbe\xbd\xfbqs\xd6\r\xd0 \xe9h\x02g\x06'\x98\xc1\x10(\x18\x10\x00\xfb\x03(P\x0eTd\xb3S\x9aa\x17}\x0f[\xa5\xefS8d\x05\xf0ۿQ3\x10\xfb\xa8Z|\x03\x94t\aJ\xf2\x95P\xb0\xbe\x85\x9d\xb1X\x1f&\x85\xe8\x03F6#˥\x9d\x88\xeb\xa4w\x02\xfc\xb5\xec\xadDA#\xaaB\x02\xeep\xe4\a\x9b\x81\x0e\xf0;\xe0\xce\x10D\f\x11\t]\xd1\xd9Yb\x90 \xe5\x86\x1d\u0530\xc1(i\x80:\x9fl#b\xdccd\x88\xa8}\xeb̿\x87\xdc$\fɢV\xf1(\x87c3\x8e1:ea\xafl\xc27\xa0\\\x03\xbdz\x84\x88\x99\xa7\xe4N\xf2\xe5\x10\xaa\xe1w\x1f\x11\x8c\xdb\xf9\x15t́V\xcbekx,*\xed\xfb>9Ï\xcb\\\x1ff\x9b\xd8GZ6\xb8G\xbb$\xd3V*\xea\xce0jN\x11\x97*\x98*Cw\xb9\xb0\xea\xbe\xf9.\x0eeH\xafϰ\xf2\xa3Ȍ8\x1aמ\fd\xcd?q\x02\xa2\xfa\"\x982\xb5\xec\xe2H\xb4t\t;\xeb\x0f\x9b/0.\x9d\x0fc\xca~Q\xcea\"\x1d\x8f@\b3n\x87\xb1\x1cbV\x9e\xe4D\xd7\x04o\x1c\xe7\x0fm\r\xba)\xfd\x94\xb6\xbda\x1a\xc5,gU\xc3Mv\x1a\xd8\"\xa4\xd0(Ʀ\x86[\a7\xaaG{\xa3\b\xff\xf7\x03\x10\xa6\xa9\x12b_v\x04\xa7&9\r.\xac\x9d\f\x8cNv\xe5\xbc&\xa5\xbe\t\xa8\xe5\xf4\x84@\x99ivF\xe7Ҁ\x9d\x8f\xa0\x8e\x95?\x10X\x9fe\x9e\xaf\xdc\fN\xc5\x16y\xda;\xc1\xf2%\a\xc9\xf2\x0f\x9d:7\x9a\xef\xb1nk\xf1\n\x1a\x80\x14\xf7\xf8\xa1\xbe\xc8x\x1d\x03̪w\x16\xc9(b\xa1Ax\x15+\x10\x93:\xc5t\xb9\xb44t\xa9\x9f_\xa0\x82_3揾}r\xfc\xc6;\x16\xb9?\x19t\xe7m\xeaq\xe3T\xa0\xce?\x13{\xcbؿ,r\xbc\x90\x0f\x97\xd4e\xe0\x1a\xc5\xca\xf1\xfa&\x86\x805R\xb2W\x97\xbb\xd9\xdc~\xcb>\xae\x84?\xc9ԕ\xda\x19[\xbe#\x9f\x17\x82ܲ\xa3\x10dJ\xb98\x10\xe4\xed\x11\x1d2\xd2\xd1\xc3\x1e\fw\xb3\x19\x01\x1e:\xa3\xbb<1\xabH\xec\x91\xc8k\x93\xcd\xe6\xdb\xe1K\xf1\x99\x883J\xae\xb2\xc2g\xba\x05\xfcE\xf7\x15˸\xb6@5\x94\xf1\x8bl\x87\x15'\xfa\x06\xe3\xc9\xf1#\xd5:ň\x8e\x87,\xf9\"\x9eNx\xa9\xf3\x8c\xe5\xfa\xc7\xfa\xe33\xf6\xf3\xfe\x18\x99\x9f\x9aʸ\x82&D\xacȴ\xf2|\x9011\xa0l\f\x97d\x94v\xfe\x9c9'j\xf6D\xf1k01\xdb\xec3\x10?\x1c\x02\x8bK\xa2+7\xe0\xf4\xc1\x96\x13\"\xe5ׅV\xd3w\x8d\xb4-B\x83\x16\x19\x1b\xd8>\x16\xbb\x7f$\xc6\xfe\x12\xf7\xce\xc7^\xf1\n\xe4f\xac\xd8\xcc\xc8H\x1e\xd5jkq\x05\x1c\xd35\x95\xcdn\xf38\xfa\x01\xed\xe4\xfa\x8b\x99\xdb\x1e\xd1~[aޞ\xc3\xf4\xee\xfb\xe0@\xcb\x1a[\xb6\x8e+\x95F\xf9\xf6\xf6\xfa\xfe\xcfw\xa3a\x00m\x94F\xe3Dr\xe8\xe1\x1bı\xc1(\x8cY}A\x04\xc3*\xe0\x14\xc0\xd0\x06\xab\bc\xc8#\x86 \x0eaIu\rZ\x94nȒ\xf4\xa9-0\tj\xf3\v\x96\xae\x80;4D&\t\xa6Tr\x87Ɓ\xc1RUR\xfc\xb7\xa7mI\xd7\xe8І9\x8cq\xe5\xf0y\xd7/Y\x03;\xd6t\xf8\x1a\x98\xe4в=\x18\xa4S\xa0\x93\x03z~\x89-\xe0ge\x10\x84ܪ5\xd4\xcei\xbb^\xad*\xe1R\xfc.U\xdbvR\xb8\xfdʇb\xb1\xe9\x9c2v\xc5q\x87\xcdʊj\xc9LY\v\x87\xa5\xeb\f\xae\x98\x16K\x0f]\xfa\x18^\xb4\xfc\x1b\x13#\xbe\xbd\x18a\x9d)F\xf8|x=!\x01\n\xb0 ,\xb0\xb85\xdc\xe2\xc0\xe8\xe4 \xdf\xff\xe3\xee\x03\xa4\xa3\xbd0\xa6\xdc\xf7|?l\xb4\a\x11\x10Ä\xdcbt0[\xa3ZO\x13%\xd7JH\xe7\x7f\x94\x8d@9e\xbf\xed6\xadp$\xf7\xffth\x1dɪ\x80K\x9fԐ\xc3\xec4i./\xe0Z\xc2%k\xb1\xb9d\x16\xbf\xba\x00\x88\xd3vI\x8c}\x9a\b\x86\xf9\xd8tq\xe0\xda`\"%MG\xe45Ʉ\xee4\x96$=b \xed\x14[\x11=\x14\xb9s6]^\x8c\b\xe7\r\x97\xbe\xacw\x9a.\x82\\p\x99\xecI\xd8\xe4\xc0\xa7&\x87\x19VΈ\x024S/\xdb\xef\x19F.\x1b\x1dl1\xa3pD\f\xf4I\xc5\xf1\xcc=n\x14\xc7\x1cl\xda\n\xaefA[)\xe3#\x7f\xd4I9?\x85>%\x9f\x05L+~\x06W<\x91\x81\xc1-\x1a\x94%&\xc7u*\x9d\xc9 \x1b&\x1as\x8cǕ\x02Nx\xf5,ⷷ\xd7ɓ'&F\xecn~\xee\x19\xfeз\x15\xd8p\x1f\xe8Ο}q\xbd\r\x87y\x9f\xe6\x140\xd0\x02Cb\xda\a\t\x10\xd2:d\x1c\xd46K\x91\xca' \xc37\x18w\xbc\x0e\x1e,\xba\xcaCh!\xde\x03#\xdf)8\xfc\xeb\xee\xdd\xcd\xea\x9f9\xd6\xf7\xb7\x00V\x96h}^\xee\xb0E\xe9^\xf7\xa5\x02G+\frJ\xfc\xb1h\x99\x14[\xb4\xae\x88g\xa0\xb1\x9f\xde|\xces\x0f\xe0Ge\x00\xbf\xb0V7\xf8\x1aD\xe0x\uf593\xd2\b\x1b\xd8\xd1S\x84G\xe1j1\r\xa6=\aH\xbd\xe2\xb5\x1f\xfdu\x1d{@P\xf1\xba\x1dB#\x1ep\r\xaf|Zs\x80\xf9+\xd9\xceo\xaf\x8eP\xfdS0\xedW\xb4\xe8U\x00\xd7\xc7\xe1\xa1\xd1\x1d@\x06\xcb3\xa2\xaa\xf0\x90UM?\x1fT\xc8U\x7f\v\xca\x10\a\xa4\x1a\x90\xf0\x84Iz\xc1Q\"\x9f\x81\xfe\xf4\xe6\xf3Q\xc4c~\x81\x90\x1c\xbf\xc0\x1b\x10\xb1\xd8Ҋ\x7f[\xc0\a\xaf\x1d{\xe9\xd8\x17:\xa9\xac\x95\xc5c\x9cU\xb2ه\xf6\x881\tʄyp\xcdL\ueffa*\x13C;C\x88\xf6\xcb\xd8\xf6[2\xc9\xe9\xffVXG\xe3/\xe2`'\x9ed\xbe\x1f\xaf\xaf~\x1f\x05\xefċl\xf5H\x02\x1etd\xd8\xe58\x93\x98\xbd\x1f-N\xa9c&c\xed\xd7<+3t\xacʤb\xc3\xf6䩄\xed$\aƭ\x18VY`\x06\x81A\xcb4I\xee\x01\xf7\xcb\x10\xe25\x13\x14\x9f)\x04\xf7}\x0e`Z7\"\x1b\x8ac \x8fIh\xe4\x04\x15ڬ\xb2\xc7\ue795ð\xafsF\n\x1f\aK\x93\f\xcet\x96\\\x9d\xb3\xd4Q\xbfi\x8e\x16e\xd7Ρ,\xe1Ai\xc12\xe3\x06\xad\x13ef\xe2\xd5<\xd38!\xac\xc0\xcb3<\x88-\xe8L\xf1\x12E\x112\xbd\xbe\x80\xf1]\xc7\\\x85p\xbc<8\n\x91*t\xca[\xc7\x10\x97\xf9Rr\xb2\x86J\xabɐV|1ed\xa6\xf3\x98&G\x9d\xd1!\xd2y}\xed\x1b\xdeϨ\xb0C#?\xf24\xf8S\x97\xda\xfbTL\xbc\xb4\xc6.\x15\xe5\xe9㧕\xd3⽜\xef\xf0\xed,ã\xba\x8b\x96\xacw\xd0\xf6\x8fg\xe4\x8ad\x18\x90\v;}\x04#j\xc8}\x12M9\xfe\x96\x89\x069\xa4\xb7\x9d\xe9\x9e\f\xd5!\x95\rn\xc9\xdd\a\xd3K\xa5i\x84\xd7'\xaa5\x82\xf5}\xa2\v{\x82fg\x91\xfb\x9eF\x86\t\xf3\xe4u\xabL\xcb\\\xe8k.\xb3De\xd74l\xd3\xe0\x1a\x9c\xe9\xe6\xd3',\xb1EkYu\xce\x14\x7f\x0e\xabB\xc5\x1e\xb7\x00ۨ\xce\xf5%\xfb\xc8=^بS\xcf\xeb\x1ad\x8b\xe1\xb1:3*VlLڛ\xc6\xef\x19:\x82Ã\xa0G\xb5\xc1|\xd0\x7f\x89O\x00\xf0\x0fZ\xe7\x10Қ\x9c\x81\xf5\xde뤅\xc1\t\xa7|\x83\x8f\x99\xd1\xd9C\xdcp\xf22\x99Lf\xeeGo\rϺ\x7f<\xe8\x1c\v\xe22\xa8U\x93\x8cY9ր\xec\xda\r\x1a\xe2\xc3f\xefЎ\xddy\xae?\xe3\xeb\xba\x03\x1b\a\xfb\x93\xfc\x02\xa5X\xaa\x96L\xfa>*Y\x97S\xc0\x85\xd5\r\xdbg\b\xa7\x8b\xf8܍\x8c\x8b\\\xc0A\x9f\x93Qk4~\xea\xb9}%\x8f\xe9J\xc9#\x95F\xb2g!\xdd_\xffr\"\xd3\x13\xd2a5\t\x0eq\x9e\xd8\xf9\x03\x9d\xf2uN8\x91\xc4Xɴ\xad\x95\xbb\xbe:\xa3\x05w\xfd\xc2d\r\xb3\xe79\xec\xa9EUȉ\xaa\xf7-\xcf2\xd5\xf1\x13\xf09\xa8\xa3\xc5g\xa2P||\xceŠ;\xd4̐\xa5\xfb7\x81\xcb\xe9\xa3\xd5k\xb0\xc27:)\xf3\f\xa9hhCX\nN\x94Z)\x83\x19\x97\t\xf3\xb02\n\"c\xf8\xbfg\xfc\xc8\xea\xc9l\xd0#\xe7\x03ڱY>\x1c\xe96\xfdC\xd0\x1a~\xfdmqHlXI\xc5\x13\xf2\x9b\xe9\x1fYĔ3\xfdՄ\xffY*\x19*\t\xbb\x86O\x9f\x17\xe9\xd9\xf2>\xfd1\x04\r\xfe/\x00\x00\xff\xff\xb0\xddǼ\x99\"\x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4YKs\x1b\xb9\x11\xbe\xf3Wti\x0f\xcaV\x99\xc3]'\x95\xa4x\xb3\xa5lJɮ\xac2e]\\>\x80\x83\xe6\f\x963\x00\x02`H3[\xfb\xdfS\x8d\a9/\x92\xa2*Z\xcf\xc5\x16\xd0h|\xf8\xd0/4'\xd3\xe9t´xBc\x85\x92s`Z\xe0W\x87\x92\xfe\xb2\xd9\xfa\xef6\x13j\xb6\xf9q\xb2\x16\x92\xcfᦱN\xd5\x1fѪ\xc6\xe4x\x8b+!\x85\x13JNjt\x8c3\xc7\xe6\x13\x00&\xa5r\x8c\x86-\xfd\t\x90+錪*4\xd3\x02e\xb6n\x96\xb8lD\xc5\xd1x\xe5i\xeb\xcd\x0f\xd9߲\x1f&\x00\xb9A\xbf\xfcQ\xd4h\x1d\xab\xf5\x1cdSU\x13\x00\xc9j\x9c\x83V|\xa3\xaa\xa6F\x83\xd6)\x836\xdb`\x85FeBM\xacƜv-\x8cj\xf4\x1c\x0e\x13aqD\x14N\xf3\xa0\xf8\x93\xd7\xf31\xe8\xf1S\x95\xb0\xeeߣ\xd3?\v뼈\xae\x1aê\x11\x1c~\xd6\nY4\x153\xc3\xf9\t\x80͕\xc69\xdc\x13\x14\xcdr\xe4\x13\x80H\x80\x876\x05ƹ\xa7\x94U\x0fFH\x87\xe6\x86T$*\xa7\xc0\xd1\xe6Fh\xe7)\xdb\xeb\x01\xb5\x02W\"m\xe9\xe9fB\nY\xf8\xa1\x00\x01\x9c\x82%BD½2\x80_\xad\x92\x0f̕sȈ\xb8L+\x9eɤ3\xca\x04\xce\xef{\xa3nG\xe7\xb0\xce\bY\x1cC\xf6\x7f\x06\xd5\xc1\xf3\xa0\xf83\x91<\x96\xe8e\x12\x9aFW\x8aq4\xb4y\xc9$\xaf\x10\xc8r\xc1\x19&\xed\n\xcd\x11\x14i\xd9\xe3Nw\x91|J\xfaZ3\x97\xb0s\t\x15A\xb6\xb3\xfdS{\xe8ܾ\x0f\x8a\xc7\x05\x10\x8d\x1a\xacc\xae\xb1`\x9b\xbc\x04f\xe1\x1e\xb7\xb3;\xf9`Ta\xd0\xda\x11\x18^<\xd3%\xb3]\x1c\v?\xf1\xba8V\xca\xd4\xcc\xcdAH\xf7\u05ff\x1c\xc7\x16\x17eN9V\xbd\xdf9\xb4\x1d\xa4\x8f\xfdဖ\x9c\xad\x88\xd7\xffM\xe0.\tҭ\x92]^\xdf\xf7F\xc7\xc0\xb6\x94\xa6@\x9c\r\x82hG뻢\xab\x8f3\x17\x06\xc2\xf4\xe6\xc7\x10\xca\xf2\x12k6\x8f\x92J\xa3|\xf7p\xf7\xf4\xe7Eg\x18@\x1b\xa5\xd18\x91\xa2k\xf8ZY\xa55\n]f\xafIa\x90\x02N\xe9\x04mp\x8a0\x86\xa3f5\xff\xce\xc4\xfck\xaf;X\aN\x17>\x9f\xebN\xdc\x00%;\x10\x16X\\\x1aNq :\x85\xec\x8f\xffX\xdc\xcf\xfe9\xc6\xfc\xfe\x14\xc0\xf2\x1c\xad\xf5\xf9\x1ak\x94\xee\xcd>gs\xb4\xc2 \xa7\xc2\x05\xb3\x9aI\xb1B벸\a\x1a\xfb\xf9\xed\x97q\xf6\x00~R\x06\xf0+\xabu\x85o@\x04\xc6\xf7\xe1/ٌ\xb0\x81\x8e\xbdF\xd8\nW\x8a~\xd2\xda3@\xd6\x15\x8f\xbd\xf5\xc7ul\x8d\xa0\xe2q\x1b\x84J\xacq\x0eW\xbe\x12<\xc0\xfc\x8d\x1c\xeb\xf7\xab#Z\xff\x14\x1c芄\xae\x02\xb8}\xbek{\xe4\x01\xa4+\x99\x03gDQ\xe0\xa1\x10\xed\x7f>xSH\xfc\x1e\x94!\x06\xa4j\xa9\xf0\x8a\xe9\xf6B\x00\xfd\xf9헣\x88\xbb|\x81\x90\x1c\xbf\xc2[\x102p\xa3\x15\xff>\x83Go\x1d;\xe9\xd8W\xda)/\x95\xc5c\xcc*Y\xedB\xb5\xbfA\xb0\xaaF\xd8bUMC\xbd\xc1a\xcbv\xc4B\xba8\xb27\x06\x9a\x19w\xd2ZS\x95\xf1\xf8\xe1\xf6\xc3< #\x83*|\xbc\xa3\xec\xb4\x12T5P\xb9\x10r\x9e\xb7\xc6A\xd2L\x9fm\x82\xf98\x05y\xc9d\x81\xe1\xbc\b\xab\x86\xb2Pv\xfd\x12?\x1e\xa6\xfe\xf4\x8d\x94\x00\xfd\xc0\xf1͒\xe83\x0f\xe7+\xd5g\x1c\xae\xfd\xd6:y\xb8u\xb3D#ѡ?\x1fW\xb9\xa5\xa3娝\x9d\xa9\r\x9a\x8d\xc0\xedl\xab\xccZ\xc8bJ\xa69\r6`g\xfe\xc9<\xfb\xce\xff\xf3\xe2\xb3\xf8\xd7\xf5s\x0f\xd4y\xf4\xbf\xe6\xa9h\x1f;{ѡR\xad\xf8\xfc2q5\xacTN0\x11\f\xe0\f\a\xb1\x994\xf22\x8a\xf6\x13*E?B\xaf\x11oE\xe3!\xf6R\xbb\xa2\x874\x95\xbd]\x84\xd3\xf1\xf7^OF+>\xe9\x93\xd6v\xc9\xde\xe4\xc1\xa1\xfa\x13][\xed\xcdv\x9a\x9c\xed\xd3\f\x9fʾ\x83v\xc9c9t\xed\"\xef!f\xbb\xd4ˣ\aˋ\x9f˹\xa2\xc7@\xf7W\x8b\xd36p3\\\xe1{S\x86G\x9f\x105\xfa7hh8n\x99M\x9b\x8c\xdd7\xb4\xf4\x85\xa5>O\x92:\xe4\xbeT\xa7\x97Ċ\x89\n9\xec\x7f7\xf1\xcdq\xeb\x9b4\xd7c\x95iR\xd4X\xe4>n\x8c\x80\x1e\xaeK}O\xce\x1cNI\xc5@B6UŖ\x15\xce\xc1\x99f8}½j\xb4\x96\x15\xe7\xfc\xeb\x97 \x15^\xf1q\t\xb0\xa5j\xdc\xfe\x19\x1f\x1d-Rqm\xa3\x15\\\xd6J(\x99=\a\xe5\x81d\xc6,n\xef\xf2\xa7M\x0eN\x84\xb2{\u070e\x8c\x0e\xfa\xd0\xedɛdB#s?y븈\x80\xb8\xd19\x0e\xa2\x18\x94\xaaJ֭\x1c%\xa5\xa6^\xa2!\"|\xf3;1\x92\x02\xc7X_Ŀ\xa7\x0eL\x1e4\xa4X\x18T\xc5\x17bΤo\x13\x92\xfd:\x05\\X]\xb1݈\xdet\x12_2\x91\xf9\x92\x1f\x1d,&y!\xb9\xbf\x9f\xbb\xb4\x9f\xb3o\xee\x8f\x17tc?\x15\x8c\xddB\xbb\xefߛ\xdf\xff\xaa\xf1:;\x9c(\xe2\xacc\xc6=7\xec-:\xc2\xe7\"\x9eW=\x1e\xefڡk\x18\xa8\xba\xdb\xfc\x911j\x94\xa8\xc1\xa0G\xce[\xbac/\xb4=\xd2,\xf7\x9d\xfe9\xfc\xf6\xfb\xe4\x90\xeeXNU;\xf2\xfb\xfeOڱVI\xbfP\xfb?s%\xc3O\xcav\x0e\x9f\xbfL 6M\x9f\xd2\xcf\xce4\xf8\xbf\x00\x00\x00\xff\xffe\xe5\xd5&\b \x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xdc\x18\x93Z\x98\x92\x9a\x01fAW֬#D{zSI\xa4\xbd\x16ĺv\xacV\x95\x1f{h\xe0:\x14\x8fS\x04V\xcc`\x0e*\x88}%Є\xb5r\xc7\xfeV\xb1\\\x8d\x82n\x90\xf7\xae\x86`+\x14`P`f\x95\x1eR2\x85\x9e\xfeIQ\x96#t\x8c\xa8;\xfc\xb7\x88M\x80\x04\x12\xf3\xe7-϶\xde\v \xd9tp Wh\x9c\xe6 Ou?\x86$\xcc\xf1>,2\xa5;\xdag\xe6L\x1d\u008b\xe9\x93\xf6Iз\xed3\xa3y\a\x9a%\xbc\x8f\x9a\xce\xf6\xf9\xf7$lmJN\x10ڻ\xc1\xd4\xd7\x15Z\x17U\x91\xb7\x7f\xb7\x06,J\xbb\xbf\x02n\xeb\xb7s\x10\x99\x10\x9d\xf5\xffƌ9^\xe2\xef\x0eg\xbe\xaa\xc4Ore\x0e\"q\xa5Y\xfeo\xc8\x14g,\x1e\x83\xadHfȯ\xddYW\xc0\xd7\rC\xf2+XsaQ\x1fp\xe6E\xe7\xe55\x88\x91b\xef\xe8)\x98Ͷ\uffd0gc\xda\fT\"]\x0e'{\x9f\xb8\x0e\x12\xfa\x86y\x06.\xb8\xf8\x95k,|\\\xfc\xc9Q\xb3}\xe3\x02\x8aw\x1f~\xc4|\x8a<\x90&y\x03D\xde\x1dl\xb6\xbbtp\xf4S\xd1\b\xaeO\x134\xf9\x8c\xc7\x150x½\xf7X\x98\x04b\x0e\xb3\xceߍ\x86OC\xe2\xb8ԋw\x8fq\xef\xc0\x84\\\xca\xec\xecTQ\xf0\xcf\x13F\xfc\xfd\xd8\xd3# \xed)D\xb8\x9e\x92\xf4\xc2\x11\xc2E\xde\xe9\xc4\x03\x97\x17\xabu\xd1\xb7]o\x87@\x9c\xd5ۡ\x05\x1aCwJj\xe6\xae\a\x80Le\xed8\xbb\xbd7Rs\x84\xe7\xb3B\nP1\xf7I/2\x9f\xc1\x8f\xf6\xbdK#e\xf0(vǻ.I\x9c\xedNH\xcf\x12}Y\xb4\xed\n\v\x97\x14\xd4;\\T\xf2I\xaag\xb9p1\xa5\x99\xcd\xd67\x8b\x9f\xac8\xbe\xa6\xd2\xe8\x8bW\xba\b\xd4\xf6\xf7\fJ!\x99\xcdG\x05\xc6SR0\xa7\x86|\x1b\xebȟ\xb3\xbb\x98Z\x7fbr\xa89\xde\xfa\xfe\xd3\xd4ަ\xbb\xf8\xac\x8e\xff\xf0\xbcE\xbbE]7\xb6.\\\x0foL\xad\xb6\xa5\xc9\xd6\x15n\x9a\x9dH~jo\xca7\xe1\x1d\xb4?\xc5}eY\tqE\x82\xcd*a}+\xaa\xae\"B\x94\xd4\x03\xb4RJ ;l\x8bM)\xa2ϕ\xce\xfb\xfd`M\xe9\xban\bS\xf5\"\x11\f=/}\xd7g\xb7.ۯ\x81\xbb\xecO\xbdӿ\xbcU,\xa1\xbc=SԞn\xa0\x9b\xa2\xd7Pl\xba\x14ke0\x8c\v\x9d\x95\xdf\x14\xf9f\n\xd1\xe3\xe5\xe7\x90lE\xcbvo\x97\xfd\x7f\xac\n\xc5h\x97Y\x88\xa0\xf2\xbcm\xf2\x04\xce\xf2ʜ\xefx^1ѓ\xc0\x0e\xcdZ҂\xd2 \xb9\x88ա\x88\xe6\xf5\xfc\x1e\x8d\xe1c\xe9\xf3\xd1G\x9f\xd5iw'\xadf}r\xa5\xba_\x89\x1e\xd1\xe0Ǧf\xd3[\xf2\xd2k\xd1\xd3\xc5\xe3c*Ї\xf5\xe5Q\xa0\xf3u\xe7\x14Ou\xa6\xc6|Be9\xb1\xab\xe8\xc5\t\xe8\x94\xda\xf1I\x15\xe3\xd9ƛ\xc4:q\xbf\x02<\r\xf2\x88\xeap\x12q\xe6+\xc1G\xd7\x7fC\xbdu\x12\x8f\xe4\xaao\xa4\x9e;\tx\xb4\xd6;Uŝ&y\xa4\u009b^\xbb\x9d\x04\xed\xea\xba\xf3\x15\xdb\xd7\xeb\xcbz\r\x17y\\\xd5\xccV]_\xe4B'\xd4U\x8f\xa9\xa6\xceR\xec\xc4\xcaiS\x19\x1dY\xf7\xd8zi\xbf\x1e:\x024\xa5J:R\x05\x1d\x818Y\x1bM\xad}\x8e\xc0\x9e1\xbb\x93R2\xf1g\xe3u\xff\xc6ʒ\xcb͐\xf3\xa9\xf21)\x1b\x83\xd2iw͞pt\x9d\xe3^X\x11[\xd2\x7f\x90\x18\tA\xea\xa4\x13\x97V-\xe1\x9d\xdc\x0f\xe0\xbaf\xe8\xa8\xcb\xdd\xffb\x85\xb6\xf5̅\xe8~\x95\xe1\xc0vA\x85\x0f\x9cL<\x10\xa6\x81c\x1f3E\x99\xa2t\xcfߝ\x8b8>\x1e\f鈴\xa6\xfd\xe7\x98\xeb\xcc\xed\xf6D\xff\xb9\xa8\x84\xe5e\xf4\x10\x97Z\xed\xb8K\x8amq\xdf\xd0\xf3\x0f徇X\xed\x1d\xa4\x8f\x0f\xcd\xf9Z\x1e\x84\x02,v*\x9eQ\b`f\x88~\xe6\xbf\t\xcc\xd4\xc2}\xe6C\x9c\xac\xe5!|;x\xe5\xce`,@\x95\xf5\xd7j\x05\x81q\xdf\x15\x9aH\x02`ԺL{\xb8\xde\x19w\xef\xfe\xacP\xefA\xed\\\x154\xb8<3\r\xc7^S\x98J\xb4\x1d\x1eA\x01\xfa/Q\x0f<\xffVc\xc0;\xe9mp\x14\xec\xc1\x1e\x1d\x1cRZm\xb4C\xfa\x99\x02\x99\x91\xa1Q\xa8R5\xb3\xe3\xf20ijR\xbbu\xcf\x1b\xfb\x1c\x1f\xfd\xcc\xfa\x1dg\x89\x80N\x8f\x81&@\xa6vߦe\xecg\xbbm\xcf\x15\v\xcdEC\xc9n`Z7\xed9\xbah\x8f\xe8\x9e=\"*:..J&SJ\x97\xecY\xa2\xa33\xc6G爐N\x8b\x91f@\x1et\xbf\xa6\xf4\xb5&\x15\x99\x92K\x14)E\xa5\xf9\xba\xe6t\xbfjB\x9fjB\xf1cn\xa7\t\xfd\xa8\xc7\xf5\xa1&\xd0\xf0L\xd1ә\xe2\xa7sDP獡f\xa3\xa8Yə\xfc\xfb\xe4\x1cy]M\xfd\xa0r\xbcW\xda\xce9\xfc\xf7\x87\xe3#\x15\xacN\x10\xa4D\x0e\xb2\x1e\x1aA\xca\xf9\xf2\xc1\x8f?\r\xa9x\xb1)\xac\x7f\xffy\x0e\x9f\x87f\xe04\"\xe4\x92\xd6\xf1Y\x04\x0f\x9a\xefp1\x92\x95f\xab,|\xb7\xe3,\\(\xa2\xaa<\x04!\xfa\xfbs`\xf9h\x99\xad\x12\x11\xf5c{\xb8\xf2l۩\xe7G\x10\xb2\x91\xa0\xb1R\xab+\x10\x85\xc4\xef\x0e\x15\xfde\xd7O\xffj\xd7R_\xeeߟ=I\x95^\xc1ui\x9dο\xa1եI\xf0#n\xa5\x92Nju\x96\xa3\x13\xa9p\xe2\xea\f@(\xa5\x9d\xa0ϖ\xfe\x04H\xb4rFg\x19\x9a\xd5#\xaa\xf5S\xb9\xc1M)\xb3\x14\r\x03\xaf\xa6\xde\xff\xb4\xfe\x97\xf5Og\x00\x89A\x1e~/s\xb4N\xe4\xc5\x15\xa82\xcb\xce\x00\x94\xc8\xf1\nl\xb2ô\xccЮ\xf7\x98\xa1\xd1k\xa9\xcfl\x81\t\xcd\xf6htY\\A\xf3\x83\x1f\x140\xf1\xab\xb8\v\xe3\xf9S&\xad\xfb\xa5\xf3\xf9\x93\xb4\x8e\x7f*\xb2҈\xac5\x1f\x7f\xb5R=\x96\x990\xcd\xf73\x00\x9b\xe8\x02\xaf\xe03MU\x88\x04\xd33\x80\xb00\x9ez\x05\"M\x99T\"\xfbj\xa4rh\xaeuV\xe6\x15\x89V\x90\xa2M\x8c,\x1c\x93\xe2\xce\tWZ\xd0[p;l\xcfC\xed7\xab\xd5W\xe1vW\xb0\xb6\xdco]섭~\xf5$\xf2\x00\xc2'w ܬ3R=\x0e\xcd\xf6\x01\xae\x8dV\x80\xdf\v\x83\x96P\x86\x949\xab\x1e\xe1y\x87\n\x9c\x06S*F\xe5\xdfD\xf2T\x16\x03\x88\x14\x98\xac{x\x06L\xba\x1f\xe7p\xb9\xdf!d\xc2:p2G\x10aBx\x16\x96q\xd8j\x03n'\xedh\x1ao`%2؋\xac\xc4\v\x10*\x85\\\x1c\xc0 \xcd\x02\xa5j\xc1\xe3.v\r\xff\xa1\r\x82T[}\x05;\xe7\n{uy\xf9(]\xa5b\x13\x9d祒\xeep\xc9\xdaRnJ\xa7\x8d\xbdLq\x8f٥\x95\x8f+a\x92\x9dt\x98\xb8\xd2\xe0\xa5(\xe4\x8aQW\xacf\xd7y\xfa\x0f\x15G\xed\xbb\x0e\xaeG\xfb\xcd7V\x84\x13\x1c \x8d\xe8\x05\xc6\x0f\xf5\xabh\bM\x9f\x88:\xdfn\xee\xee\xdb\xc2$m\x9f\xfaL\xf7\x96\x845, \x82I\xb5Ű\xa3\xb7F\xe7\f\x13UZh\xa9\x1c\xff\x91d\x12U\x9f\xfc\xb6\xdc\xe4\xd2\x11\xdf\x7f/\xd1:\xe2\xd5\x1a\xae\xd9\xee\x90\x1c\x96\x05\xed\xc0t\r\xb7\n\xaeE\x8eٵ\xb0\xf8\xea\f J\xdb\x15\x116\x8e\x05m\x93\xd9\xef\xec\xa9\xd6\xfa\xa12o#\xfc\xaa\xf6\xf8]\x81Ig\xcb\xd08\xb9\x95\to\f֞\xb5\n\xe8iP߆w-\xff\xc2j\xaa\xff\xb5\x87\x87\xd7eլh\xc9~\xb8\x1ds\xb81c$W\x1e\x1a\xe9\x14\xa5\xfb\xdc\x1d҂-J\x04(3\x98t\xb5^\xac};\x82\tAխGp<\xe2*\xff\x84yAjc\x06\xc5\xfbЍP$\xfa\xa4\xb5;U\x19\xfeJ\xcd\xea\xa0]\xe1H\xb9\xf1t;$\xbe\xede\x1a\xb4\xd7\x11Wa\x92\xb3\xd4\x12+\xef\x94(\xecN;\xb2q\xbatC\xbdz\v\xb8\xbe\xbb\xed\rjq\x9e\xb0b\x1bΌv\x1a\x9e\x85<\xe6\xb4o$\x97\xd7w\xb7\xf0@.\x11V0\xc1[rp\xa5Q\xac\x8e\xbf\xa1H\x0f\xf7\xfaW\x8b\x90\x96\xac\x95*\xbb|1\x02x\x83[\xda\xf4\x06\t\x06\r@ch\x0fXFM\x97n\xcd\x0eG\x8a[Qf.(9i\xe1\xfdO\x90KU:<\xe6;L\xf3\xde\x13\x89\xc1\xf9\xd5\xd8{\xfd\xb3\xf5\x8c\x8c \xe9Ǒ\xa1\x03[\xaa\xd0)\xec\xb9\xdf\x18Ue\x86`\x0f\xd6a\x0e\x9b\x00\xa5\xb6\xd5\xcc\x15\xd6\aY\x16\xc0X\xd8\x1c*܇\xd7M^\xb8\xd8dx\x05Δ\xc3\xd3Nm\xdd!\xda|C\xebd\x12A\x99\xf3>i\xfc\xc8\x01\xc2\x18\xfea\x84(=\n\x90\x91\x17O\xe4h\x06\n\x91\xb7\x90e-\xe2\xceS\x05\xe0\xbf\x14|$\x03\x97\x90ٹ\n\xe6Lb\xc6&TiȴzD\xe3g$W\xe1Yf\x19oi\xcc\xf5\xbe\xe3d\xb5\x1b\xd9\x16\x83\x19\x19Iؖdv\xd6@\xb2?*#RY\x87\"]\x9f\xbf\x16\xf3\xf0{\x92\x95)\xa6u\x983\xa8Kz\x8c\xbb9\x1a\xc4\x01\xa1\x90\x8a43\x85_DtU\xff:B\r\xf65\x85A2\x18 \x95\x87I\xa4!E\xb3\x19Q\xd2Ԥ\xc3|\x04\xcfٝ\xbc\x80j\xc2\x18q\x98\xa0Y\x154/!Y=&\xb8b\x99L\x90\x88U;\\L5&\xcd\xc8\xfa\xfe\x1f\x12l\xa7\xf5S\f\x91\xfe\x9d\xfa5\x8e%$|6\x01\x1b܉\xbd\xd4\xc6\xf6\xa3\x13\xfc\x8eI\xe9Fw\x9bp\x90\xca\xed\x16\r\xc1›\x8e\xbf\xa7\x885mV\xa9\x99i\xc6\x1f\xad\xaba:1\x8f\xa91\xb6\x14v_F\xa1\x02#NV\x8fuC*\xf72-E\xc6jB\xa8įO\xd4\xf8\x8d)\xb7\x19\x818\xc2\xdf+\xa3j\x15ĥ\x8eW\xaa\x15\x92ۗk3f\xb7|;\x063N\x86\x8d`gr̅k\x9a)3\xb4\x01\x15o\xfe\x1a\xbds\xd1p\xca\at\x99\xd8`\x06\x163L\x9c6\xe3\xe4\x89\x11\x02\xdfb\xf5\xe7\be\a4i\xd7ߚU\xa2M#\x87l'\x93\x9d7V$e\f\vR\x8d\x965\x86(\x8a\xec0\xb5h\x88\x91\x8c0ٜ\xd2hZ\x84\xfa\xe8\xc3\x1dS$M\x8b\xd4\xc1M\x9b\xd1\xc6]\xaa\xd7b\xf3F\xf4\x0e\x9aꇄ\xfd\xf6h\xf8\xcb\v;\x91[\xa2]\xc3\xed\x160/\xdc\xe1\x02\xa4\xab\xbe\xc6@%W\xb1\xc1\xe3oƸ\xd3v\xcbm\x7f\xf4\x8b\xef\x96\x17\xe1Z\x8d\xc6߄il\xac\ue0adZİO\xed\x91\x17 \xb75\xc3\xd2\v\x8a!\x1d\xb2/5\x87h\xcbљ\xe5\xdcK\x12(\xd6\xf6R˅Kv7\xf51PĈ\x1e\xad\xfa\x00\xbc_^\xc50̃\b\x90P;\x15|\x82)\r\xe6\xfed\xf4\x9e\xf7G\xf3\x85=\xc0\x0f\x9f?b:G2\x88\x97ԣE}\xe8y:m\x14x\x81Q [\x8bb7\xad\x8e\xf1\xfc\xf9\xf7\x05\bx\u0083\xf7\xac\x06\x83ˡF\xac\x155H\x83|\x18\xcfj\xe4\t\x0f\f*\x9c\xaeG\xc1[\"*\xbe=\xe1!\xb6k\x8f\xa8\x84_8\xd7\xf3ԥ\x0f\xbc\x8a\x98\xadԴ\x9a\xa8a\xef\x80\xd3q\x8b\x85eJ\xa9j\x15\xc5O\\vͰΕ\xd2\x13\x1e\xdeY\xcf>\xda5;Y,\xa0\x00)l\xb0\xc8;\xac\xbaKy\x10\x99L\xeb\xc9x\x9f,\x80x\xab.\xe0\xb3v\xf4\x9f\x9b\xef\xd2\x12\x8a*\x85\x8f\x1a\xedg\xed\xf8˫\x92\xd8/\xe2D\x02\xfb\xc1\xbc-\x957\vD\x97E\xf378\xb0\t%\x11\xad\xd9&-\xdc*\x8a\xcf<}\x96\xb0i\x87\x15r\x1e\xad\xbc\xb4|\x1b\xa3\xb4Z\xb1\x99\xaef[\x00\xb4\x8dW`\x956\x1dN],\x848\x88b@\uf7ac\x95\xff\xe5\xe8\x1ek\xaa\x19,2\x91`Z\x9dJ\xf3\xa5\x99p\xf8(\x13\xc8\xd1<\"\x14d7\xe2\x85j\x81&\xf7\xed\x04)\x8cw-\xaa\x16\xcc\xc2\xc0\x1d\xd0P[Ѯ\x8f\xecY\xb19\xaa\xfb\xc8\r\xd9t\xf7\xb8U\xb2yg\x7f(\x8a\xfa픎e\x96e!\xbf\x8e}\x10\x8f\xa4w?r\xc1\xc7\xd6\x7f\x90ye\xf1\xfe3\xce\x1a\ni\xec\x1a>pBK\x86\xed\xf1\xd5)ak\xaa(\x90\x84\x89\xb4@r\xb2\x17\x19\xb9\x0f\xa4\xbc\x15`\xe6\x9d\t\xbd=\xf2\xa0\xe2T\xcc\xf3N[o\xf3\xebc\xf5\xf3'<\x9c_\x1ci\xaf\xf3[u\x1e\a\x93t\xfe\x91Ҫ\xbd\x16\xad\xb2\x03\x9c\xf3o\xe7\xec\x98-\xd9\"'8o\v\xa4:\xba+\xa7\x8e,\t\x05(֮\xbc\x16\x1a\\'X\x90\v?\xb7\x8ah\x99.\xb4\x1d\xb9]\x1cA뫶\xce\x1f\x00v\xdc\xed\x81\x13\u0098\xe8/\x9c\x1a\x82\xd8:4`\x9d6U2\x03\xa9\xdd\xde\x019q\xde\xce\xf3\x9eX]\x9fFz\xc0\x14d\x9e7\x1a\xc2\xeb\xf4s\x9f\xe5@\xff?\x0f3ag\x89a\x17F'h\xed\xbc(EZ\x8e\x99\x03\xdb\xfa\xb0V\xf8\xe0m\x1b\xa5\x9ac\x8e\x92\xab\xb6\xcc\x15'Ҟ\x10\xd8\xdc|o\x9d;\x93\x1a\xa2\xbfcD\xf9\x14\x1c\x81\x13\x1d\xf3\\\xf4\x13k\xa2ѽ\xf6\xa3\xab\r\x18\x80\xf9\x80\xc9<\x96\xacT\x96\xf9\xcdA$\xffj\x8eG.\xd5-O\x04\xef_\xcdY\x81J\x95㩡\xccu5\xbeaH\xfd!6~\x85*=C\xf3]\x8d\xc1\x0eg\x8fo2\xe29\x05\xe4L+\xedڇ5a\xa6w\x16\xb6\xd2X\xd7 \xbc\x00\xaa\xb4|M\xfd\xba1\xa6\xba1\xe6\xe4\x10\xf3\x8b\x1f\xdd:V\xdc\xe9\xe7\x90Դ$\xb0\xae\x88\xbf\x13{\x04\xb9\x05\xe9\x00U\xa2K\xc5\a^\xa4.h\x9a\x05\x10=\x13\xbd1\x89\xb4\x99\xad\xc1\xaa\xcc\xe3\t\xb2b\xe9\x94j\xf6t\xac=\xe4g!\xe3N\xa7\xe04\xb6\xba\xa9ġ\xa1\xd6͆\n\x19D\xed\xec\xb5\\|\x97y\x99\x83ȉ-K\xe2ƭ\xcf=\xaaR\xdd<\xaf\x9f\x85t!\x83\xd8_\xac.Ӧ\x89\u038b\f\x1dVYE\x89VV\xa6X\xbb\x0f\x81\xff\x839ZcM\xc0VȬ4\vt\xf4b\xce,\x8dۂzz\xf9`,\x1e\x91\x15\x133\xf2\xd0}\x81\xd3\x1f\xa8\xc5\xe7\xa5-\xcdF\xab\xf3\x87\xe6\xad\xca\x0f\xe4\xa0-{ 0\x9bo\x16\x834\xc4d\x99\r\xe7\x8f\xcd@]\x92[\x16\x1b\x83G\xe4\x91\xc5g\x8fő\a\xf8\xb5}l\xceX\xb4\xd7\x16\x9b\x1f\xf6:Ya\x91\xb9`\xad\f\xafY\x90'f\x80E\x13,.\xdb+:ǫ\x95\xb95O\xad\x89̮\xe1|\xadY\x90C\xf9\\1YZQ\xb8F\xe7f\xd5\x19W\xf3'\x89?\x94\x91\xf5\xf2\xb9\xdf/\xe9\xe7O\xe7WEeUE\xc5\x02\xf38G\xe5M-͖\x8a\xa2\xea\xd2̨:\xebib\xe2\xa8|\xa8\xe3\\\xa7\xa9\xa5\xccfA\x8dg8M\x81\x1d\xca}\x8a\xc8k\x9a\x00\xd9\xcexZ\xec\x06\xccJ\xd3L\x87\xe1\x8a\x18U\x9b\xb7\xb5\xd9\xff\x85\x04\xfe袵\xe9\xb8\xc01Qɗ\xde\x10\xe2}\xe5\xf5\r\xb9\xd5\xe31\x9ew\xb6Op\xabG@\xden!/3'\x8b\xacU\x92\xc2\xed\xf0P?y\xffM\xf3\xd3\xcb́\xa1}\xf9V\v\xf0\x18\xc8n\x80 ,\xe8\a]?\xfc\xcc%\xfa\x1a\x7f\xd1]R\xec\x15|\xdc5\xcf\xfc\xf3\x95\xc8g+\x91\x97@1\xd8G>OY\xfe,%\x92\xce'\x06[\x93SG>?Y\x14n\x9d\x18pMB\x9czn2\x1drM\x1f\xa7\xf5\x9f\x99\x9c\xe0NDH\xd8l\x97\x1f\xbe\x11\xd0&E3{\xb9\xb2D4g\x85\xb2\x17\x13u\xe7\xefU\x1d\xa8\xaa{Q\xaf\xf6\xc5\xcd\x18wt\xfd\n>\x81_\xa4J=oH\b[\xfeE\xe7\xf6\xa7q~\xc6\xee\x80\xda\x1eg(\x8b鯍,\x16\x82\x14)\x87E|\xbdm\xd7p#\x92]=\xc3\bH\x9ew',l\xb5Ʌ\x83\xf3\xfa>\xee\xd2O@\x7f\x9f\xaf\x01~\xd6\xf5Eh\xab\xca\xcd\bT+\xf3\";P\xf4\x03\xe7m0?&8\xa3\xc2gC\xb9\xbfP\xcf,\"\xfe\xbd\xeb\x8e\x18*7\x19ʺ%\x99.\xd3z\x86\tf\vu\x80\xaf\x0f\xecKq\t\xa8\xa4)\x95\x15<\xa5*\x0e\xeeU\xd2\x1a\x019V5r\x11\xc9Ư\x86\xad\xd3F<\xe2'\xed\v{\xc6Ь;\xa2S\xdb5h\xaa*Q$\xbc\xfc\x1aYYU\x92\xb9\x0f\xb0\xc9\x1f;\xaa5H؎\xa9\xb0\x99]\xee\\\x16\xb1\xb8\xfb\xfbO~AN\xe6\xb8\xfeX\xfak\xfaU!\x8cE\xa2t\xb5P?h3n\xe5v\xfa\x99\x8b\xf5\xb5\xabo\xb6\xea\x1b#\xe7\xabqF\xc0I\xab\xd9w\xea[V\xa4\x8b\x11\xf9\x87\xe1\x91-\xa5\xd2b\xe2\xd4žގ\xc2\x12\xd6\xeaD\xb2\x1e\xe2\x03!N\x13{\xbdBqS\xf6dBe\x94\x16\xbf<+4ߪ\x8djo\xd5Xy\xcd\x0e\t\x7f=\x1a8ZZ\xd3i\xd6~\xbd\xeeCVO\x05\x02Y_\x8a\xb4:ْ\xb6.@{L\xba\x99\xfd?\xbe\xf7\x87=\xd7\xd5p\xcd\xd7U]\x86\xf6,\x82\xb2\xbe\xd4jL\x85a_\x935\x11\x85+M0\xaeIi\xb8j\x1e\x01A_T\xee\xb4\x1a\xc3M\xcd\xf6\x19^6UܛX\x7f\xb6f\xfc\x00\xff\xea*\xc1\xa3es\xbde\xf55\xddW\x04\xff4v\x0e\xee\x03\xae28WQ\x99\xfa\xd4)\xbe\x81\xd0<\xb0\xaaNx7\x86\xfap\xce\xe6\n>\xe3\xf3\xc0\xd7\x1bE\x8b8\xbeQ\U000c9658\xf2\t\xc1P}\xf5\xc9%\xee\xebQ\x9c\x15;\xa0-\xbaj\xae\u05fd\x97nõj\xeb.>\x03v\x88\xad\xff(\xb7\xfe\xf8&\xa15\xfd\xd3Q\x8fQ\xc55\xa9\xb4\xc6\x14\xd6\xe0\x96:\xfah\xd1\xec\xb98l%$\xc1\x86\xb7\xbf\x94\x9b\xa6X$\xfc\xf1\xe7Y\xb3+E\x92`\xe1BZW\xfb߲8\xf7\xc5^\xab\x7f\xaa\x82\xffL\xb4\xf2n\xb6\xbd\x82\xff\xfc\xef3\b\x06\xf8\xa1\xfa\xf7(\xe8\xe3\xff\x06\x00\x00\xff\xff6\x91\a\xfc\xf9c\x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4VO\x8f\xeb4\x10\xbf\xe7S\x8c\x1e\x87w!\xe9{\xe2\x00\xca\r\x15\x0e+`\xb5\xda>\xed\x05qp\x9di;\xacc\x9b\xf1\xb8K\xf9\xf4\xc8v\xb2m\x93\x94]\x90\xf0-\xf6\xfc\xf9\xcdo\xfed\xaa\xba\xae+\xe5\xe9\t9\x90\xb3-(O\xf8\xa7\xa0M_\xa1y\xfe.4\xe4V\xc7\xcf\xd53ٮ\x85u\f\xe2\xfaG\f.\xb2\xc6\x1fpG\x96\x84\x9c\xadz\x14\xd5)Qm\x05\xa0\xacu\xa2\xd2uH\x9f\x00\xdaYag\fr\xbdG\xdb<\xc7-n#\x99\x0e9\x1b\x1f]\x1f?5\xdf6\x9f*\x00͘տP\x8fAT\xef[\xb0ј\n\xc0\xaa\x1e[\b\xc8II\x94\xc4\xc0\xf8G\xc4 \xa19\xa2Av\r\xb9*x\xd4\xc9\xf1\x9e]\xf4-\x9c\x1f\x8a\xfe\x00\xaa\x04\xb4ɦ6\xd9\xd4c1\x95_\r\x05\xf9\xe9\x96\xc4\xcf4Hy\x13Y\x99e@Y \x1c\x1c\xcb\xfd\xd9i\r!py!\xbb\x8fF\xf1\xa2r\x05\x10\xb4\xf3\xd8B\xd6\xf5JcW\x01\fLe[\xf5\xc0\xc5\xf1s1\xa7\x0fث\xe2\x04\xc0y\xb4\xdf?\xdc=}\xb3\xb9\xba\x06\xe80h&/\x99\xef\x85Ȁ\x02(\x18P\x808PZc\b\xa0#3Z\x81\x82\x12\xc8\xee\x1c\xf79G\xaf\xa6\x01\xd4\xd6E\x019 d\x05\xd9*\x03Ge\"~\r\xcavЫ\x130&/\x10텽,\x12\x1a\xf8\xc51f2[8\x88\xf8ЮV{\x92\xb1\xeb\xb4\xeb\xfbhIN\xab\xdc@\xb4\x8d\xe28\xac:<\xa2Y\x05\xda\u05ca\xf5\x81\x04\xb5Dƕ\xf2Tg\xe86w^\xd3w_\xf1Ч\xe1\xe3\x15V9\xa5\xca\n\xc2d\xf7\x17\x0f\xb9!\xfe!\x03\xa9\x1dJ}\x14\xd5\x12ř\xe8t\x95\xd8y\xfcq\xf3\x05F\xd79\x19S\xf63\xefg\xc5pNA\"\x8c\xec\x0e\xb9$qǮ\xcf6\xd1vޑ-ե\r\xa1\x9d\xd2\x1f\xe2\xb6'\tc\xed\xa6\\5\xb0Σ\b\xb6\b\xd1wJ\xb0k\xe0\xce\xc2Z\xf5h\xd6*\xe0\xff\x9e\x80\xc4t\xa8\x13\xb1\xefK\xc1\xe5\x14\x9d\n\x17\xd6.\x1e\xc61w#_\vݽ\xf1\xa8S\x06\x13\x89I\x9bv\xa4s{\xc0\xce1\xa8%\x95\xe6]H\xb2ƿ\xc42L\x92\x82f2_R\x7f\xbe\x8dfy\x9c䗃\n8\xbd\x9c`zH2S\xff\x86v\xa8O\xda`1Q\xa6\t\xbe\r%\x1d\xb4\xb1\x9f\xfb\xac\xe1\x1e_\x16n\x1fإɚ\xe7\xfa\xf5\xb9Q\x1bP\xfe7{\xb2\xb3p\xa7\x91\x15\xa9\xfc\x0f\xbb\x1c\xd5\x17\x03z0\x04\x1c\xadM};\x9b\x90\x19\xc8t\x92\xcfdH\xb0_@\xb3\x88\xe7\xce\xee\\\xde\x04Tr\xac\xa4\xf4\x13\x0e\xc9\x1e\xfc\x14\\\v\x06o纜\xf9\xf0z\x17\xa1\xe5\xe4?\xe9\x7fSN\xe3\x86\x18\x17}\xd7\x19\xd5\xe2C\xf2\xb8\xc4\xf8r\x7f\r(\xa31jk\xb0\x05\xe18\xd7.\xba\x8aY\x9d\xa6U3\x96\xday\x9fz\xa3\x80f\n\xa9O^\x0ehou\x03\xbc\xa8锿\xf2\f\xdb\xd3-\xd5\xf5\xebr8o\xa9R\xba-\xa4\xd9]\v-p\xf6.R\x16\xb3WJzq\xf3\x98\x11\xb2\xb9\x94\x1dg\xc6Uk\x8c\x8b\xc8<\x86\x9b\x10\x16\x93=\xbb\xcc滋\xf0\x828V\xfb1\xe0\xf3\xe8M\x9b\x9a\x17\xec\xee\xa7+\xee\x87\x0fW\xbbj\xfe\xd4\xcevT6t\xf8\xf5\xb7\xaaX\xc5\xeei\\0\xd3\xe5\xdf\x01\x00\x00\xff\xff\xfb\xb1p\x12\x1b\f\x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4WO\x8f۶\x13\xbd\xfbS\f\x92\xc3^\"9\xc1\xef\xf0+t)\x82M\x0fA\xf3g\x11o\xf7R\xf4@\x93#\x8b]\x8aTgHm\xddO_\f)\xad\xbd\xb6\x9cl\x8aV\x17C\x149|\xf3\u07bc!\xbd\xaa\xaaj\xa5\x06{\x87\xc46\xf8\x06\xd4`\xf1ψ^\u07b8\xbe\xff\x81k\x1b\xd6\xe3\x9bս\xf5\xa6\x81\xeb\xc41\xf4_\x90C\"\x8dﰵ\xdeF\x1b\xfc\xaaǨ\x8c\x8a\xaaY\x01(\xefCT2\xcc\xf2\n\xa0\x83\x8f\x14\x9cC\xaav\xe8\xeb\xfb\xb4\xc5m\xb2\xce \xe5\xe0\xf3\xd6\xe3\xeb\xfa\xff\xf5\xeb\x15\x80&\xcc\xcbom\x8f\x1cU?4\xe0\x93s+\x00\xafzl`\f.\xf5\xc8^\r܅\xe8\x82.\x9b\xd5#:\xa4P۰\xe2\x01\xb5콣\x90\x86\x06\x0e\x1fJ\x88\tW\xc9\xe9.G\xdbL\xd1>L\xd1\xf2\x04g9\xfe\xfc\x95I\x1f,\xc7\xe8\xcdK\x9a,\xcaWO\xb0ƽT\x11G\xb2~w\xf4!\x1b\xe1+\n\x88\aJ!\x94\xa5%\x8b\x03\xd12$\xec|\xf9is\v\xf3\xd6Y\x8cS\xf63\uf1c5|\x90@\b\xb3\xbeE*\"\xb6\x14\xfa\x1c\x13\xbd\x19\x82\xf51\xbfhgџ\xd2\xcfi\xdb\xdb(\xba\xff\x91\x90\xa3hU\xc3u\xeeB\xb0EH\x83Q\x11M\r\xef=\\\xab\x1eݵb\xfc\xcf\x05\x10\xa6\xb9\x12b\x9f'\xc1q\x03=\x9d\\X;6\xd8\xd4\xde.\xe8\xb5\xec\xe4̀\xfa\x89\x81$\x8am\xed\xe4\xec6\xd0\t\xafj\xf6\xf9r\xbc\xfa\xc9\xf4e\x83C\xe9\xfe\xadݝ\x8e\x02(c\xf2١\xdc\xcdŵ_!l!\xef뼓\x14j\x1bH\x10\x8d\xd6 Us\x9e\x13\x92DS\xc2\x16\x9d\xe1\xfa,\xe4\x05\xces*\x84F4V\xee\x1c\xe8S$\x8f\x13\xf3᧬/\x94\x1f\x02\xe4ң~\xea\xb1>\xa27\xb9\xa9\x9f\xa1\t\xb9\x86\x19\r<\xd8\xd8\x15s\xb8\xe3C\xeay*\xc8s\x8f\xfb\xa5\xe1\x13\xec\xb7\x1d\xca\xcc\xd2N\x11\x185a\x14\x1c\x8cN\xcc+ά\x01>&\xce\xf6R\x8b\x11AZ\x845\xf3\xea{ܟ\x13\r\xdf\x12w:\xef\xbf\r\xf9J\xce\xc5\x190a\x8b\x84>.Z\\\xee\x1e\xe41bv\xb9\t\x9a\xc5\xe0\x1a\x87\xc8\xeb0\"\x8d\x16\x1f\xd6\x0f\x81\xee\xad\xdfUBxU\n\x81\xd7\xf9ް~\x99\x7f.\xa4|\xfb\xf9\xdd\xe7\x06\xde\x1a\x03!vH\xa2Z\x9b\xdc\\hG\xa7ݫ\xdcq_A\xb2\xe6ǫ\x7f\xc2K\x18\x8as\x9e\xc1\xcd&W\xff^N\xee\fJ(\xda\x14U\x02\x81\xf4M\x11\xbb\x9f\xd4,\xfda\xa9\x10gL\xdb\x10\x1c\xaa\xf3ғ\xeek\t\xcd9\xa4Jv\xf8\x1e\x9b\xcd\xce\xfd\x86\xc9n\xa6ibx\xc9j^6\x17B\xb9\x97\xe4[\x8a\xda\xe1%\xa3/p\xbc\x9cJ\xf5\xb8\xc1\xb3ZtT1\xf1\xf77\xe9\xbcl\x9a\xb9\x9d\x1a\xb5N$\x05=\xc5\\\xb8\xd0\xfc;\x8dz\xe8\x14/\xb8\xed\x19\xa8od\xe5,\x83\xb3-\xea\xbdvX\x02Bh\x17\xaa\xe9\xbb ˃>\xf5K\xa5\xf5vT֩\xadÅo\xbfxu\xf1\xebE\xf1\x17\xf5<\x1bd\xb9\xb5\x98\x06\"\xa5\x12{\xaa\xb2i䠾\xd2\xd2\\\xd0|:\xfd\xdb\xf1\xe2œ\x7f\x0e\xf9U\a_\xceDn\xe0\xd7\xdfV%*\x9a\xbb\xf9\xa2/\x83\x7f\a\x00\x00\xff\xff\xe4\xf3S\x85\xb2\r\x00\x00"), } var CRDs = crds() diff --git a/go.mod b/go.mod index a6a7e61297..e4f5a88771 100644 --- a/go.mod +++ b/go.mod @@ -34,8 +34,8 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.7.1 github.com/vmware-tanzu/crash-diagnostics v0.3.7 - golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 - golang.org/x/net v0.0.0-20220615171555-694bf12d69de + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 + golang.org/x/net v0.7.0 golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb golang.org/x/sync v0.0.0-20210220032951-036812b2e83c google.golang.org/api v0.74.0 @@ -132,9 +132,9 @@ require ( go.uber.org/zap v1.21.0 // indirect golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect golang.org/x/exp v0.0.0-20210916165020-5cb4fee858ee // indirect - golang.org/x/sys v0.0.0-20220614162138-6c1b26c55098 // indirect - golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/term v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect @@ -150,5 +150,3 @@ require ( sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect ) - -replace github.com/gogo/protobuf => github.com/gogo/protobuf v1.3.2 diff --git a/go.sum b/go.sum index 520335524b..339e4b7f59 100644 --- a/go.sum +++ b/go.sum @@ -293,6 +293,9 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= @@ -475,6 +478,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.15.1 h1:y9FcTHGyrebwfP0ZZqFiaxTaiDnUrGkJkI+f583BL1A= @@ -842,8 +847,9 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -904,8 +910,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220615171555-694bf12d69de h1:ogOG2+P6LjO2j55AkRScrkB2BFpd+Z8TY2wcM0Z3MGo= -golang.org/x/net v0.0.0-20220615171555-694bf12d69de/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1027,14 +1033,14 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220614162138-6c1b26c55098 h1:PgOr27OhUx2IRqGJ2RxAWI4dJQ7bi9cSrB82uzFzfUA= -golang.org/x/sys v0.0.0-20220614162138-6c1b26c55098/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= -golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1043,8 +1049,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1055,8 +1062,10 @@ golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= diff --git a/hack/build-image/Dockerfile b/hack/build-image/Dockerfile index b3c5e42a04..0522f7793c 100644 --- a/hack/build-image/Dockerfile +++ b/hack/build-image/Dockerfile @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM golang:1.18 +FROM --platform=linux/amd64 golang:1.20.3-bullseye ARG GOPROXY @@ -20,12 +20,6 @@ ENV GO111MODULE=on # Use a proxy for go modules to reduce the likelihood of various hosts being down and breaking the build ENV GOPROXY=${GOPROXY} -# get code-generation tools (for now keep in GOPATH since they're not fully modules-compatible yet) -RUN mkdir -p /go/src/k8s.io -WORKDIR /go/src/k8s.io -RUN git config --global advice.detachedHead false -RUN git clone -b v0.22.2 https://github.com/kubernetes/code-generator - # kubebuilder test bundle is separated from kubebuilder. Need to setup it for CI test. RUN curl -sSLo envtest-bins.tar.gz https://go.kubebuilder.io/test-tools/1.22.1/linux/amd64 && \ mkdir /usr/local/kubebuilder && \ @@ -58,7 +52,7 @@ RUN wget --quiet https://github.com/goreleaser/goreleaser/releases/download/v0.1 chmod +x /usr/bin/goreleaser # get golangci-lint -RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.45.0 +RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.51.0 # install kubectl RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl diff --git a/hack/build-restic.sh b/hack/build-restic.sh index 5859414fd3..d6a233f4a5 100755 --- a/hack/build-restic.sh +++ b/hack/build-restic.sh @@ -50,6 +50,7 @@ fi mkdir ${build_path}/restic git clone -b v${RESTIC_VERSION} https://github.com/restic/restic.git ${build_path}/restic pushd ${build_path}/restic +git apply /go/src/github.com/vmware-tanzu/velero/hack/fix_restic_cve.txt go run build.go --goos "${GOOS}" --goarch "${GOARCH}" --goarm "${GOARM}" -o ${restic_bin} chmod +x ${restic_bin} popd diff --git a/hack/docker-push.sh b/hack/docker-push.sh index d9f1835b8b..d675bffd34 100755 --- a/hack/docker-push.sh +++ b/hack/docker-push.sh @@ -56,6 +56,18 @@ elif [[ "$triggeredBy" == "tags" ]]; then TAG=$(echo $GITHUB_REF | cut -d / -f 3) fi +# if both BRANCH and TAG are empty, then it's triggered by PR. Use target branch instead. +# BRANCH is needed in docker buildx command to set as image tag. +# When action is triggered by PR, just build container without pushing, so set type to local. +# When action is triggered by PUSH, need to push container, so set type to registry. +if [[ -z $BRANCH && -z $TAG ]]; then + echo "Test Velero container build without pushing, when Dockerfile is changed by PR." + BRANCH="${GITHUB_BASE_REF}-container" + OUTPUT_TYPE="local,dest=." +else + OUTPUT_TYPE="registry" +fi + TAG_LATEST=false if [[ ! -z "$TAG" ]]; then echo "We're building tag $TAG" @@ -90,11 +102,9 @@ echo "BUILDX_PLATFORMS: $BUILDX_PLATFORMS" echo "Building and pushing container images." -# The use of "registry" as the buildx output type below instructs -# Docker to push the image VERSION="$VERSION" \ TAG_LATEST="$TAG_LATEST" \ BUILDX_PLATFORMS="$BUILDX_PLATFORMS" \ -BUILDX_OUTPUT_TYPE="registry" \ +BUILDX_OUTPUT_TYPE=$OUTPUT_TYPE \ make all-containers diff --git a/hack/fix_restic_cve.txt b/hack/fix_restic_cve.txt new file mode 100644 index 0000000000..03e29777b5 --- /dev/null +++ b/hack/fix_restic_cve.txt @@ -0,0 +1,60 @@ +diff --git a/go.mod b/go.mod +index 5f939c481..6f281b45d 100644 +--- a/go.mod ++++ b/go.mod +@@ -25,12 +25,12 @@ require ( + github.com/spf13/cobra v1.6.1 + github.com/spf13/pflag v1.0.5 + golang.org/x/crypto v0.5.0 +- golang.org/x/net v0.5.0 ++ golang.org/x/net v0.7.0 + golang.org/x/oauth2 v0.4.0 + golang.org/x/sync v0.1.0 +- golang.org/x/sys v0.4.0 +- golang.org/x/term v0.4.0 +- golang.org/x/text v0.6.0 ++ golang.org/x/sys v0.5.0 ++ golang.org/x/term v0.5.0 ++ golang.org/x/text v0.7.0 + google.golang.org/api v0.106.0 + ) + +diff --git a/go.sum b/go.sum +index 026e1d2fa..da35b7a6c 100644 +--- a/go.sum ++++ b/go.sum +@@ -189,8 +189,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL + golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= + golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= + golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +-golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= +-golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= ++golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= ++golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= + golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= + golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= + golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +@@ -214,17 +214,17 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc + golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= + golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +-golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +-golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= ++golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= ++golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= + golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +-golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg= +-golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= ++golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= ++golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= + golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= + golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= + golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= + golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +-golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= +-golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= ++golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= ++golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= + golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= + golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= + golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= diff --git a/hack/release-tools/chk_version.go b/hack/release-tools/chk_version.go index b0c7ecd8d7..32736e12e9 100644 --- a/hack/release-tools/chk_version.go +++ b/hack/release-tools/chk_version.go @@ -24,6 +24,7 @@ import ( // This regex should match both our GA format (example: v1.4.3) and pre-release formats (v1.2.4-beta.2, v1.5.0-rc.1) // The following sub-capture groups are defined: +// // major // minor // patch diff --git a/hack/update-3generated-crd-code.sh b/hack/update-3generated-crd-code.sh index fb7be399eb..9f9242619c 100755 --- a/hack/update-3generated-crd-code.sh +++ b/hack/update-3generated-crd-code.sh @@ -25,16 +25,17 @@ if [[ -z "${GOPATH}" ]]; then GOPATH=~/go fi -if [[ ! -d "${GOPATH}/src/k8s.io/code-generator" ]]; then - echo "k8s.io/code-generator missing from GOPATH" - exit 1 -fi - if ! command -v controller-gen > /dev/null; then echo "controller-gen is missing" exit 1 fi +# get code-generation tools (for now keep in GOPATH since they're not fully modules-compatible yet) +mkdir -p ${GOPATH}/src/k8s.io +pushd ${GOPATH}/src/k8s.io +git clone -b v0.22.2 https://github.com/kubernetes/code-generator +popd + ${GOPATH}/src/k8s.io/code-generator/generate-groups.sh \ all \ github.com/vmware-tanzu/velero/pkg/generated \ diff --git a/internal/restartabletest/restartable_delegate.go b/internal/restartabletest/restartable_delegate.go index f91a3eb37f..37f73e4c39 100644 --- a/internal/restartabletest/restartable_delegate.go +++ b/internal/restartabletest/restartable_delegate.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/apis/velero/v1/backup.go b/pkg/apis/velero/v1/backup.go index f3b2725425..6568f830f7 100644 --- a/pkg/apis/velero/v1/backup.go +++ b/pkg/apis/velero/v1/backup.go @@ -113,8 +113,8 @@ type BackupSpec struct { DefaultVolumesToFsBackup *bool `json:"defaultVolumesToFsBackup,omitempty"` // OrderedResources specifies the backup order of resources of specific Kind. - // The map key is the Kind name and value is a list of resource names separated by commas. - // Each resource name has format "namespace/resourcename". For cluster resources, simply use "resourcename". + // The map key is the resource name and value is a list of object names separated by commas. + // Each resource name has format "namespace/objectname". For cluster resources, simply use "objectname". // +optional // +nullable OrderedResources map[string]string `json:"orderedResources,omitempty"` diff --git a/pkg/backup/backup_test.go b/pkg/backup/backup_test.go index 56cbfe2146..46aecf7799 100644 --- a/pkg/backup/backup_test.go +++ b/pkg/backup/backup_test.go @@ -1002,7 +1002,7 @@ func TestBackupResourceCohabitation(t *testing.T) { }, }, { - name: "when deployments exist that are not in the cohabitating groups those are backed up along with apps/deployments", + name: "when deployments exist that are not in the cohabiting groups those are backed up along with apps/deployments", backup: defaultBackup().Result(), apiResources: []*test.APIResource{ test.VeleroDeployments( @@ -1046,11 +1046,11 @@ func TestBackupResourceCohabitation(t *testing.T) { } } -// TestBackupUsesNewCohabitatingResourcesForEachBackup ensures that when two backups are -// run that each include cohabitating resources, one copy of the relevant resources is +// TestBackupUsesNewCohabitingResourcesForEachBackup ensures that when two backups are +// run that each include cohabiting resources, one copy of the relevant resources is // backed up in each backup. Verification is done by looking at the contents of the backup // tarball. This covers a specific issue that was fixed by https://github.com/vmware-tanzu/velero/pull/485. -func TestBackupUsesNewCohabitatingResourcesForEachBackup(t *testing.T) { +func TestBackupUsesNewCohabitingResourcesForEachBackup(t *testing.T) { h := newHarness(t) // run and verify backup 1 diff --git a/pkg/builder/container_builder_test.go b/pkg/builder/container_builder_test.go index a5c85a909a..d2d48dbdc1 100644 --- a/pkg/builder/container_builder_test.go +++ b/pkg/builder/container_builder_test.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/builder/json_schema_props_builder.go b/pkg/builder/json_schema_props_builder.go index 2eafb8605e..10f904cf0a 100644 --- a/pkg/builder/json_schema_props_builder.go +++ b/pkg/builder/json_schema_props_builder.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/builder/object_meta.go b/pkg/builder/object_meta.go index 6c01019b47..6df1afadc8 100644 --- a/pkg/builder/object_meta.go +++ b/pkg/builder/object_meta.go @@ -146,3 +146,10 @@ func WithGenerateName(val string) func(obj metav1.Object) { obj.SetGenerateName(val) } } + +// WithManagedFields is a functional option that applies the specified managed fields to an object. +func WithManagedFields(val []metav1.ManagedFieldsEntry) func(obj metav1.Object) { + return func(obj metav1.Object) { + obj.SetManagedFields(val) + } +} diff --git a/pkg/buildinfo/buildinfo.go b/pkg/buildinfo/buildinfo.go index b0533d0dbf..4e27cd45f1 100644 --- a/pkg/buildinfo/buildinfo.go +++ b/pkg/buildinfo/buildinfo.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/buildinfo/buildinfo_test.go b/pkg/buildinfo/buildinfo_test.go index f4c7cf9131..40631525a1 100644 --- a/pkg/buildinfo/buildinfo_test.go +++ b/pkg/buildinfo/buildinfo_test.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/client/client.go b/pkg/client/client.go index 331b786f26..dccdc05fcf 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -27,20 +27,15 @@ import ( "github.com/vmware-tanzu/velero/pkg/buildinfo" ) -func buildConfigFromFlags(context, kubeconfigPath string, precedence []string) (*rest.Config, error) { - return clientcmd.NewNonInteractiveDeferredLoadingClientConfig( - &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfigPath, Precedence: precedence}, - &clientcmd.ConfigOverrides{ - CurrentContext: context, - }).ClientConfig() -} - // Config returns a *rest.Config, using either the kubeconfig (if specified) or an in-cluster // configuration. func Config(kubeconfig, kubecontext, baseName string, qps float32, burst int) (*rest.Config, error) { loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() loadingRules.ExplicitPath = kubeconfig - clientConfig, err := buildConfigFromFlags(kubecontext, kubeconfig, loadingRules.Precedence) + configOverrides := &clientcmd.ConfigOverrides{CurrentContext: kubecontext} + kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides) + + clientConfig, err := kubeConfig.ClientConfig() if err != nil { return nil, errors.Wrap(err, "error finding Kubernetes API server config in --kubeconfig, $KUBECONFIG, or in-cluster configuration") } diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index e4f6f6a53b..df8df51e43 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/client/factory.go b/pkg/client/factory.go index 3a0933af79..aa5119c262 100644 --- a/pkg/client/factory.go +++ b/pkg/client/factory.go @@ -77,11 +77,10 @@ type factory struct { } // NewFactory returns a Factory. -func NewFactory(baseName, kubecontext string, config VeleroConfig) Factory { +func NewFactory(baseName string, config VeleroConfig) Factory { f := &factory{ - flags: pflag.NewFlagSet("", pflag.ContinueOnError), - baseName: baseName, - kubecontext: kubecontext, + flags: pflag.NewFlagSet("", pflag.ContinueOnError), + baseName: baseName, } f.namespace = os.Getenv("VELERO_NAMESPACE") @@ -97,7 +96,7 @@ func NewFactory(baseName, kubecontext string, config VeleroConfig) Factory { f.flags.StringVar(&f.kubeconfig, "kubeconfig", "", "Path to the kubeconfig file to use to talk to the Kubernetes apiserver. If unset, try the environment variable KUBECONFIG, as well as in-cluster configuration") f.flags.StringVarP(&f.namespace, "namespace", "n", f.namespace, "The namespace in which Velero should operate") - //f.flags.StringVar(&f.kubecontext, "kubecontext", "", "The context to use to talk to the Kubernetes apiserver. If unset defaults to whatever your current-context is (kubectl config current-context)") + f.flags.StringVar(&f.kubecontext, "kubecontext", "", "The context to use to talk to the Kubernetes apiserver. If unset defaults to whatever your current-context is (kubectl config current-context)") return f } @@ -128,6 +127,7 @@ func (f *factory) KubeClient() (kubernetes.Interface, error) { return nil, err } kubeClient, err := kubernetes.NewForConfig(clientConfig) + if err != nil { return nil, errors.WithStack(err) } diff --git a/pkg/client/factory_test.go b/pkg/client/factory_test.go index 1ea28cb874..db5edb2573 100644 --- a/pkg/client/factory_test.go +++ b/pkg/client/factory_test.go @@ -5,7 +5,7 @@ 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 +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, @@ -31,14 +31,14 @@ func TestFactory(t *testing.T) { // Env variable should set the namespace if no config or argument are used os.Setenv("VELERO_NAMESPACE", "env-velero") - f := NewFactory("velero", "", make(map[string]interface{})) + f := NewFactory("velero", make(map[string]interface{})) assert.Equal(t, "env-velero", f.Namespace()) os.Unsetenv("VELERO_NAMESPACE") // Argument should change the namespace - f = NewFactory("velero", "", make(map[string]interface{})) + f = NewFactory("velero", make(map[string]interface{})) s := "flag-velero" flags := new(pflag.FlagSet) @@ -50,7 +50,7 @@ func TestFactory(t *testing.T) { // An argument overrides the env variable if both are set. os.Setenv("VELERO_NAMESPACE", "env-velero") - f = NewFactory("velero", "", make(map[string]interface{})) + f = NewFactory("velero", make(map[string]interface{})) flags = new(pflag.FlagSet) f.BindFlags(flags) diff --git a/pkg/cmd/cli/install/install.go b/pkg/cmd/cli/install/install.go index f730bff70b..fe718ba8ad 100644 --- a/pkg/cmd/cli/install/install.go +++ b/pkg/cmd/cli/install/install.go @@ -5,7 +5,7 @@ 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 +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, @@ -323,7 +323,7 @@ func (o *InstallOptions) Run(c *cobra.Command, f client.Factory) error { return nil } -//Complete completes options for a command. +// Complete completes options for a command. func (o *InstallOptions) Complete(args []string, f client.Factory) error { o.Namespace = f.Namespace() return nil diff --git a/pkg/cmd/cli/nodeagent/server.go b/pkg/cmd/cli/nodeagent/server.go index b154f9bf81..e5e2c190cb 100644 --- a/pkg/cmd/cli/nodeagent/server.go +++ b/pkg/cmd/cli/nodeagent/server.go @@ -132,6 +132,12 @@ func newNodeAgentServer(logger logrus.FieldLogger, factory client.Factory, metri &v1.Pod{}: { Field: fields.Set{"spec.nodeName": nodeName}.AsSelector(), }, + &velerov1api.PodVolumeBackup{}: { + Field: fields.Set{"metadata.namespace": factory.Namespace()}.AsSelector(), + }, + &velerov1api.PodVolumeRestore{}: { + Field: fields.Set{"metadata.namespace": factory.Namespace()}.AsSelector(), + }, }, } mgr, err := ctrl.NewManager(clientConfig, ctrl.Options{ @@ -173,7 +179,12 @@ func (s *nodeAgentServer) run() { metricsMux := http.NewServeMux() metricsMux.Handle("/metrics", promhttp.Handler()) s.logger.Infof("Starting metric server for node agent at address [%s]", s.metricsAddress) - if err := http.ListenAndServe(s.metricsAddress, metricsMux); err != nil { + server := &http.Server{ + Addr: s.metricsAddress, + Handler: metricsMux, + ReadHeaderTimeout: 3 * time.Second, + } + if err := server.ListenAndServe(); err != nil { s.logger.Fatalf("Failed to start metric server for node agent at [%s]: %v", s.metricsAddress, err) } }() @@ -303,11 +314,10 @@ func (s *nodeAgentServer) markInProgressPVBsFailed(client ctrlclient.Client) { s.logger.Debugf("the node of podvolumebackup %q is %q, not %q, skip", pvb.GetName(), pvb.Spec.Node, s.nodeName) continue } - original := pvb.DeepCopy() - pvb.Status.Phase = velerov1api.PodVolumeBackupPhaseFailed - pvb.Status.Message = fmt.Sprintf("get a podvolumebackup with status %q during the server starting, mark it as %q", velerov1api.PodVolumeBackupPhaseInProgress, pvb.Status.Phase) - pvb.Status.CompletionTimestamp = &metav1.Time{Time: time.Now()} - if err := client.Patch(s.ctx, &pvbs.Items[i], ctrlclient.MergeFrom(original)); err != nil { + + if err := controller.UpdatePVBStatusToFailed(client, s.ctx, &pvbs.Items[i], + fmt.Sprintf("get a podvolumebackup with status %q during the server starting, mark it as %q", velerov1api.PodVolumeBackupPhaseInProgress, velerov1api.PodVolumeBackupPhaseFailed), + time.Now()); err != nil { s.logger.WithError(errors.WithStack(err)).Errorf("failed to patch podvolumebackup %q", pvb.GetName()) continue } @@ -341,11 +351,9 @@ func (s *nodeAgentServer) markInProgressPVRsFailed(client ctrlclient.Client) { continue } - original := pvr.DeepCopy() - pvr.Status.Phase = velerov1api.PodVolumeRestorePhaseFailed - pvr.Status.Message = fmt.Sprintf("get a podvolumerestore with status %q during the server starting, mark it as %q", velerov1api.PodVolumeRestorePhaseInProgress, pvr.Status.Phase) - pvr.Status.CompletionTimestamp = &metav1.Time{Time: time.Now()} - if err := client.Patch(s.ctx, &pvrs.Items[i], ctrlclient.MergeFrom(original)); err != nil { + if err := controller.UpdatePVRStatusToFailed(client, s.ctx, &pvrs.Items[i], + fmt.Sprintf("get a podvolumerestore with status %q during the server starting, mark it as %q", velerov1api.PodVolumeRestorePhaseInProgress, velerov1api.PodVolumeRestorePhaseFailed), + time.Now()); err != nil { s.logger.WithError(errors.WithStack(err)).Errorf("failed to patch podvolumerestore %q", pvr.GetName()) continue } diff --git a/pkg/cmd/cli/nodeagent/server_test.go b/pkg/cmd/cli/nodeagent/server_test.go index f540ac4fb8..5dab32b6b2 100644 --- a/pkg/cmd/cli/nodeagent/server_test.go +++ b/pkg/cmd/cli/nodeagent/server_test.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/cmd/server/plugin/plugin.go b/pkg/cmd/server/plugin/plugin.go index bb2c1c6004..2c19698d49 100644 --- a/pkg/cmd/server/plugin/plugin.go +++ b/pkg/cmd/server/plugin/plugin.go @@ -57,7 +57,8 @@ func NewCommand(f client.Factory) *cobra.Command { RegisterRestoreItemAction("velero.io/crd-preserve-fields", newCRDV1PreserveUnknownFieldsItemAction). RegisterRestoreItemAction("velero.io/change-pvc-node-selector", newChangePVCNodeSelectorItemAction(f)). RegisterRestoreItemAction("velero.io/apiservice", newAPIServiceRestoreItemAction). - RegisterRestoreItemAction("velero.io/admission-webhook-configuration", newAdmissionWebhookConfigurationAction) + RegisterRestoreItemAction("velero.io/admission-webhook-configuration", newAdmissionWebhookConfigurationAction). + RegisterRestoreItemAction("velero.io/secret", newSecretRestoreItemAction(f)) if !features.IsEnabled(velerov1api.APIGroupVersionsFeatureFlag) { // Do not register crd-remap-version BIA if the API Group feature flag is enabled, so that the v1 CRD can be backed up pluginServer = pluginServer.RegisterBackupItemAction("velero.io/crd-remap-version", newRemapCRDVersionAction(f)) @@ -220,3 +221,13 @@ func newAPIServiceRestoreItemAction(logger logrus.FieldLogger) (interface{}, err func newAdmissionWebhookConfigurationAction(logger logrus.FieldLogger) (interface{}, error) { return restore.NewAdmissionWebhookConfigurationAction(logger), nil } + +func newSecretRestoreItemAction(f client.Factory) plugincommon.HandlerInitializer { + return func(logger logrus.FieldLogger) (interface{}, error) { + client, err := f.KubebuilderClient() + if err != nil { + return nil, err + } + return restore.NewSecretAction(logger, client), nil + } +} diff --git a/pkg/cmd/server/server.go b/pkg/cmd/server/server.go index 33f43c3c20..600866fc17 100644 --- a/pkg/cmd/server/server.go +++ b/pkg/cmd/server/server.go @@ -489,31 +489,35 @@ func (s *server) veleroResourcesExist() error { } // High priorities: -// - Custom Resource Definitions come before Custom Resource so that they can be -// restored with their corresponding CRD. -// - Namespaces go second because all namespaced resources depend on them. -// - Storage Classes are needed to create PVs and PVCs correctly. -// - VolumeSnapshotClasses are needed to provision volumes using volumesnapshots -// - VolumeSnapshotContents are needed as they contain the handle to the volume snapshot in the -// storage provider -// - VolumeSnapshots are needed to create PVCs using the VolumeSnapshot as their data source. -// - PVs go before PVCs because PVCs depend on them. -// - PVCs go before pods or controllers so they can be mounted as volumes. -// - Secrets and config maps go before pods or controllers so they can be mounted -// as volumes. -// - Service accounts go before pods or controllers so pods can use them. -// - Limit ranges go before pods or controllers so pods can use them. -// - Pods go before controllers so they can be explicitly restored and potentially -// have pod volume restores run before controllers adopt the pods. -// - Replica sets go before deployments/other controllers so they can be explicitly -// restored and be adopted by controllers. -// - CAPI ClusterClasses go before Clusters. -// - CAPI Clusters come before ClusterResourceSets because failing to do so means the CAPI controller-manager will panic. -// Both Clusters and ClusterResourceSets need to come before ClusterResourceSetBinding in order to properly restore workload clusters. -// See https://github.com/kubernetes-sigs/cluster-api/issues/4105 +// - Custom Resource Definitions come before Custom Resource so that they can be +// restored with their corresponding CRD. +// - Namespaces go second because all namespaced resources depend on them. +// - Storage Classes are needed to create PVs and PVCs correctly. +// - VolumeSnapshotClasses are needed to provision volumes using volumesnapshots +// - VolumeSnapshotContents are needed as they contain the handle to the volume snapshot in the +// storage provider +// - VolumeSnapshots are needed to create PVCs using the VolumeSnapshot as their data source. +// - PVs go before PVCs because PVCs depend on them. +// - PVCs go before pods or controllers so they can be mounted as volumes. +// - Service accounts go before secrets so service account token secrets can be filled automatically. +// - Secrets and config maps go before pods or controllers so they can be mounted +// as volumes. +// - Limit ranges go before pods or controllers so pods can use them. +// - Pods go before controllers so they can be explicitly restored and potentially +// have pod volume restores run before controllers adopt the pods. +// - Replica sets go before deployments/other controllers so they can be explicitly +// restored and be adopted by controllers. +// - CAPI ClusterClasses go before Clusters. +// - Endpoints go before Services so no new Endpoints will be created +// - Services go before Clusters so they can be adopted by AKO-operator and no new Services will be created +// for the same clusters // // Low priorities: -// - Tanzu ClusterBootstrap go last as it can reference any other kind of resources +// - Tanzu ClusterBootstraps go last as it can reference any other kind of resources. +// ClusterBootstraps go before CAPI Clusters otherwise a new default ClusterBootstrap object is created for the cluster +// - CAPI Clusters come before ClusterResourceSets because failing to do so means the CAPI controller-manager will panic. +// Both Clusters and ClusterResourceSets need to come before ClusterResourceSetBinding in order to properly restore workload clusters. +// See https://github.com/kubernetes-sigs/cluster-api/issues/4105 var defaultRestorePriorities = restore.Priorities{ HighPriorities: []string{ "customresourcedefinitions", @@ -524,9 +528,9 @@ var defaultRestorePriorities = restore.Priorities{ "volumesnapshots.snapshot.storage.k8s.io", "persistentvolumes", "persistentvolumeclaims", + "serviceaccounts", "secrets", "configmaps", - "serviceaccounts", "limitranges", "pods", // we fully qualify replicasets.apps because prior to Kubernetes 1.16, replicasets also @@ -535,11 +539,13 @@ var defaultRestorePriorities = restore.Priorities{ // in the backup. "replicasets.apps", "clusterclasses.cluster.x-k8s.io", - "clusters.cluster.x-k8s.io", - "clusterresourcesets.addons.cluster.x-k8s.io", + "endpoints", + "services", }, LowPriorities: []string{ "clusterbootstraps.run.tanzu.vmware.com", + "clusters.cluster.x-k8s.io", + "clusterresourcesets.addons.cluster.x-k8s.io", }, } @@ -608,7 +614,12 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string metricsMux := http.NewServeMux() metricsMux.Handle("/metrics", promhttp.Handler()) s.logger.Infof("Starting metric server at address [%s]", s.metricsAddress) - if err := http.ListenAndServe(s.metricsAddress, metricsMux); err != nil { + server := &http.Server{ + Addr: s.metricsAddress, + Handler: metricsMux, + ReadHeaderTimeout: 3 * time.Second, + } + if err := server.ListenAndServe(); err != nil { s.logger.Fatalf("Failed to start metric server at [%s]: %v", s.metricsAddress, err) } }() @@ -921,7 +932,12 @@ func (s *server) runProfiler() { mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) mux.HandleFunc("/debug/pprof/trace", pprof.Trace) - if err := http.ListenAndServe(s.config.profilerAddress, mux); err != nil { + server := &http.Server{ + Addr: s.config.profilerAddress, + Handler: mux, + ReadHeaderTimeout: 3 * time.Second, + } + if err := server.ListenAndServe(); err != nil { s.logger.WithError(errors.WithStack(err)).Error("error running profiler http server") } } diff --git a/pkg/cmd/util/output/restore_describer.go b/pkg/cmd/util/output/restore_describer.go index 631dbb2cb0..de249c969e 100644 --- a/pkg/cmd/util/output/restore_describer.go +++ b/pkg/cmd/util/output/restore_describer.go @@ -32,7 +32,7 @@ import ( velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/cmd/util/downloadrequest" clientset "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned" - pkgrestore "github.com/vmware-tanzu/velero/pkg/restore" + "github.com/vmware-tanzu/velero/pkg/util/results" ) func DescribeRestore(ctx context.Context, kbClient kbclient.Client, restore *velerov1api.Restore, podVolumeRestores []velerov1api.PodVolumeRestore, details bool, veleroClient clientset.Interface, insecureSkipTLSVerify bool, caCertFile string) string { @@ -167,7 +167,7 @@ func describeRestoreResults(ctx context.Context, kbClient kbclient.Client, d *De } var buf bytes.Buffer - var resultMap map[string]pkgrestore.Result + var resultMap map[string]results.Result if err := downloadrequest.Stream(ctx, kbClient, restore.Namespace, restore.Name, velerov1api.DownloadTargetKindRestoreResults, &buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil { d.Printf("Warnings:\t\n\nErrors:\t\n", err, err) @@ -189,7 +189,7 @@ func describeRestoreResults(ctx context.Context, kbClient kbclient.Client, d *De } } -func describeRestoreResult(d *Describer, name string, result pkgrestore.Result) { +func describeRestoreResult(d *Describer, name string, result results.Result) { d.Printf("%s:\n", name) d.DescribeSlice(1, "Velero", result.Velero) d.DescribeSlice(1, "Cluster", result.Cluster) diff --git a/pkg/cmd/velero/velero.go b/pkg/cmd/velero/velero.go index 982761d2a9..b249416e17 100644 --- a/pkg/cmd/velero/velero.go +++ b/pkg/cmd/velero/velero.go @@ -91,7 +91,7 @@ operations can also be performed as 'velero backup get' and 'velero schedule cre }, } - f := client.NewFactory(name, "", config) + f := client.NewFactory(name, config) f.BindFlags(c.PersistentFlags()) // Bind features directly to the root command so it's available to all callers. diff --git a/pkg/controller/backup_controller.go b/pkg/controller/backup_controller.go index 2765d076ae..23f00df7a9 100644 --- a/pkg/controller/backup_controller.go +++ b/pkg/controller/backup_controller.go @@ -5,7 +5,7 @@ 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 +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, @@ -43,6 +43,8 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/tools/cache" + "github.com/vmware-tanzu/velero/pkg/util/results" + "github.com/vmware-tanzu/velero/pkg/util/csi" snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" @@ -479,11 +481,12 @@ func (c *backupController) prepareBackupRequest(backup *velerov1api.Backup, logg // validateAndGetSnapshotLocations gets a collection of VolumeSnapshotLocation objects that // this backup will use (returned as a map of provider name -> VSL), and ensures: -// - each location name in .spec.volumeSnapshotLocations exists as a location -// - exactly 1 location per provider -// - a given provider's default location name is added to .spec.volumeSnapshotLocations if one -// is not explicitly specified for the provider (if there's only one location for the provider, -// it will automatically be used) +// - each location name in .spec.volumeSnapshotLocations exists as a location +// - exactly 1 location per provider +// - a given provider's default location name is added to .spec.volumeSnapshotLocations if one +// is not explicitly specified for the provider (if there's only one location for the provider, +// it will automatically be used) +// // if backup has snapshotVolume disabled then it returns empty VSL func (c *backupController) validateAndGetSnapshotLocations(backup *velerov1api.Backup) (map[string]*velerov1api.VolumeSnapshotLocation, []string) { errors := []string{} @@ -603,7 +606,7 @@ func (c *backupController) runBackup(backup *pkgbackup.Request) error { logger := logging.DefaultLogger(c.backupLogLevel, c.formatFlag) logger.Out = io.MultiWriter(os.Stdout, gzippedLogFile) - logCounter := logging.NewLogCounterHook() + logCounter := logging.NewLogHook() logger.Hooks.Add(logCounter) backupLog := logger.WithField(Backup, kubeutil.NamespaceAndName(backup)) @@ -725,6 +728,13 @@ func (c *backupController) runBackup(backup *pkgbackup.Request) error { recordBackupMetrics(backupLog, backup.Backup, backupFile, c.metrics) + backupWarnings := logCounter.GetEntries(logrus.WarnLevel) + backupErrors := logCounter.GetEntries(logrus.ErrorLevel) + results := map[string]results.Result{ + "warnings": backupWarnings, + "errors": backupErrors, + } + if err := gzippedLogFile.Close(); err != nil { c.logger.WithField(Backup, kubeutil.NamespaceAndName(backup)).WithError(err).Error("error closing gzippedLogFile") } @@ -750,7 +760,7 @@ func (c *backupController) runBackup(backup *pkgbackup.Request) error { return err } - if errs := persistBackup(backup, backupFile, logFile, backupStore, volumeSnapshots, volumeSnapshotContents, volumeSnapshotClasses); len(errs) > 0 { + if errs := persistBackup(backup, backupFile, logFile, backupStore, volumeSnapshots, volumeSnapshotContents, volumeSnapshotClasses, results); len(errs) > 0 { fatalErrs = append(fatalErrs, errs...) } @@ -797,6 +807,7 @@ func persistBackup(backup *pkgbackup.Request, csiVolumeSnapshots []snapshotv1api.VolumeSnapshot, csiVolumeSnapshotContents []snapshotv1api.VolumeSnapshotContent, csiVolumesnapshotClasses []snapshotv1api.VolumeSnapshotClass, + results map[string]results.Result, ) []error { persistErrs := []error{} backupJSON := new(bytes.Buffer) @@ -835,6 +846,11 @@ func persistBackup(backup *pkgbackup.Request, persistErrs = append(persistErrs, errs...) } + backupResult, errs := encodeToJSONGzip(results, "backup results") + if errs != nil { + persistErrs = append(persistErrs, errs...) + } + if len(persistErrs) > 0 { // Don't upload the JSON files or backup tarball if encoding to json fails. backupJSON = nil @@ -844,6 +860,7 @@ func persistBackup(backup *pkgbackup.Request, csiSnapshotJSON = nil csiSnapshotContentsJSON = nil csiSnapshotClassesJSON = nil + backupResult = nil } backupInfo := persistence.BackupInfo{ @@ -851,6 +868,7 @@ func persistBackup(backup *pkgbackup.Request, Metadata: backupJSON, Contents: backupContents, Log: backupLog, + BackupResults: backupResult, PodVolumeBackups: podVolumeBackups, VolumeSnapshots: nativeVolumeSnapshots, BackupResourceList: backupResourceList, diff --git a/pkg/controller/backup_sync_controller.go b/pkg/controller/backup_sync_controller.go index d29a586416..5577ee9608 100644 --- a/pkg/controller/backup_sync_controller.go +++ b/pkg/controller/backup_sync_controller.go @@ -197,6 +197,7 @@ func (b *backupSyncReconciler) Reconcile(ctx context.Context, req ctrl.Request) podVolumeBackup.Namespace = backup.Namespace podVolumeBackup.ResourceVersion = "" + podVolumeBackup.Spec.BackupStorageLocation = location.Name err = b.client.Create(ctx, podVolumeBackup, &client.CreateOptions{}) switch { diff --git a/pkg/controller/pod_volume_backup_controller.go b/pkg/controller/pod_volume_backup_controller.go index 250435cd02..3fa04e1714 100644 --- a/pkg/controller/pod_volume_backup_controller.go +++ b/pkg/controller/pod_volume_backup_controller.go @@ -279,24 +279,27 @@ func (r *PodVolumeBackupReconciler) getParentSnapshot(ctx context.Context, log l } func (r *PodVolumeBackupReconciler) updateStatusToFailed(ctx context.Context, pvb *velerov1api.PodVolumeBackup, err error, msg string, log logrus.FieldLogger) (ctrl.Result, error) { - original := pvb.DeepCopy() - pvb.Status.Phase = velerov1api.PodVolumeBackupPhaseFailed - pvb.Status.Message = errors.WithMessage(err, msg).Error() - pvb.Status.CompletionTimestamp = &metav1.Time{Time: r.Clock.Now()} - - if err = r.Client.Patch(ctx, pvb, client.MergeFrom(original)); err != nil { + if err = UpdatePVBStatusToFailed(r.Client, ctx, pvb, errors.WithMessage(err, msg).Error(), r.Clock.Now()); err != nil { log.WithError(err).Error("error updating PodVolumeBackup status") return ctrl.Result{}, err } - return ctrl.Result{}, nil } +func UpdatePVBStatusToFailed(c client.Client, ctx context.Context, pvb *velerov1api.PodVolumeBackup, errString string, time time.Time) error { + original := pvb.DeepCopy() + pvb.Status.Phase = velerov1api.PodVolumeBackupPhaseFailed + pvb.Status.Message = errString + pvb.Status.CompletionTimestamp = &metav1.Time{Time: time} + + return c.Patch(ctx, pvb, client.MergeFrom(original)) +} + func (r *PodVolumeBackupReconciler) NewBackupProgressUpdater(pvb *velerov1api.PodVolumeBackup, log logrus.FieldLogger, ctx context.Context) *BackupProgressUpdater { return &BackupProgressUpdater{pvb, log, ctx, r.Client} } -//UpdateProgress which implement ProgressUpdater interface to update pvb progress status +// UpdateProgress which implement ProgressUpdater interface to update pvb progress status func (b *BackupProgressUpdater) UpdateProgress(p *uploader.UploaderProgress) { original := b.PodVolumeBackup.DeepCopy() b.PodVolumeBackup.Status.Progress = velerov1api.PodVolumeOperationProgress{TotalBytes: p.TotalBytes, BytesDone: p.BytesDone} diff --git a/pkg/controller/pod_volume_restore_controller.go b/pkg/controller/pod_volume_restore_controller.go index 5bdaf80390..8e7223e7e1 100644 --- a/pkg/controller/pod_volume_restore_controller.go +++ b/pkg/controller/pod_volume_restore_controller.go @@ -22,6 +22,7 @@ import ( "io/ioutil" "os" "path/filepath" + "time" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -122,11 +123,7 @@ func (c *PodVolumeRestoreReconciler) Reconcile(ctx context.Context, req ctrl.Req } if err = c.processRestore(ctx, pvr, pod, log); err != nil { - original = pvr.DeepCopy() - pvr.Status.Phase = velerov1api.PodVolumeRestorePhaseFailed - pvr.Status.Message = err.Error() - pvr.Status.CompletionTimestamp = &metav1.Time{Time: c.clock.Now()} - if e := c.Patch(ctx, pvr, client.MergeFrom(original)); e != nil { + if e := UpdatePVRStatusToFailed(c, ctx, pvr, err.Error(), c.clock.Now()); e != nil { log.WithError(err).Error("Unable to update status to failed") } @@ -145,6 +142,15 @@ func (c *PodVolumeRestoreReconciler) Reconcile(ctx context.Context, req ctrl.Req return ctrl.Result{}, nil } +func UpdatePVRStatusToFailed(c client.Client, ctx context.Context, pvr *velerov1api.PodVolumeRestore, errString string, time time.Time) error { + original := pvr.DeepCopy() + pvr.Status.Phase = velerov1api.PodVolumeRestorePhaseFailed + pvr.Status.Message = errString + pvr.Status.CompletionTimestamp = &metav1.Time{Time: time} + + return c.Patch(ctx, pvr, client.MergeFrom(original)) +} + func (c *PodVolumeRestoreReconciler) shouldProcess(ctx context.Context, log logrus.FieldLogger, pvr *velerov1api.PodVolumeRestore) (bool, *corev1api.Pod, error) { if !isPVRNew(pvr) { log.Debug("PodVolumeRestore is not new, skip") @@ -313,7 +319,7 @@ func (r *PodVolumeRestoreReconciler) NewRestoreProgressUpdater(pvr *velerov1api. return &RestoreProgressUpdater{pvr, log, ctx, r.Client} } -//UpdateProgress which implement ProgressUpdater interface to update pvr progress status +// UpdateProgress which implement ProgressUpdater interface to update pvr progress status func (r *RestoreProgressUpdater) UpdateProgress(p *uploader.UploaderProgress) { original := r.PodVolumeRestore.DeepCopy() r.PodVolumeRestore.Status.Progress = velerov1api.PodVolumeOperationProgress{TotalBytes: p.TotalBytes, BytesDone: p.BytesDone} diff --git a/pkg/controller/restore_controller.go b/pkg/controller/restore_controller.go index 038e971095..ad2d2e22a9 100644 --- a/pkg/controller/restore_controller.go +++ b/pkg/controller/restore_controller.go @@ -53,6 +53,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/util/collections" kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube" "github.com/vmware-tanzu/velero/pkg/util/logging" + "github.com/vmware-tanzu/velero/pkg/util/results" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -342,10 +343,12 @@ func (c *restoreController) validateAndComplete(restore *api.Restore, pluginMana } for _, resource := range restoreHooks { for _, h := range resource.RestoreHooks { - for _, container := range h.Init.InitContainers { - err = hook.ValidateContainer(container.Raw) - if err != nil { - restore.Status.ValidationErrors = append(restore.Status.ValidationErrors, err.Error()) + if h.Init != nil { + for _, container := range h.Init.InitContainers { + err = hook.ValidateContainer(container.Raw) + if err != nil { + restore.Status.ValidationErrors = append(restore.Status.ValidationErrors, err.Error()) + } } } } @@ -572,7 +575,7 @@ func (c *restoreController) runValidatedRestore(restore *api.Restore, info backu restore.Status.Errors += len(e) } - m := map[string]pkgrestore.Result{ + m := map[string]results.Result{ "warnings": restoreWarnings, "errors": restoreErrors, } @@ -584,7 +587,7 @@ func (c *restoreController) runValidatedRestore(restore *api.Restore, info backu return nil } -func putResults(restore *api.Restore, results map[string]pkgrestore.Result, backupStore persistence.BackupStore) error { +func putResults(restore *api.Restore, results map[string]results.Result, backupStore persistence.BackupStore) error { buf := new(bytes.Buffer) gzw := gzip.NewWriter(buf) defer gzw.Close() diff --git a/pkg/controller/restore_controller_test.go b/pkg/controller/restore_controller_test.go index fff95340e5..6a752c0d8f 100644 --- a/pkg/controller/restore_controller_test.go +++ b/pkg/controller/restore_controller_test.go @@ -51,6 +51,7 @@ import ( pkgrestore "github.com/vmware-tanzu/velero/pkg/restore" velerotest "github.com/vmware-tanzu/velero/pkg/test" "github.com/vmware-tanzu/velero/pkg/util/logging" + "github.com/vmware-tanzu/velero/pkg/util/results" "github.com/vmware-tanzu/velero/pkg/volume" ) @@ -506,7 +507,7 @@ func TestProcessQueueItem(t *testing.T) { sharedInformers.Velero().V1().Backups().Informer().GetStore().Add(test.backup) } - var warnings, errors pkgrestore.Result + var warnings, errors results.Result if test.restorerError != nil { errors.Namespaces = map[string][]string{"ns-1": {test.restorerError.Error()}} } @@ -864,12 +865,12 @@ func (r *fakeRestorer) Restore( actions []riav1.RestoreItemAction, snapshotLocationLister listers.VolumeSnapshotLocationLister, volumeSnapshotterGetter pkgrestore.VolumeSnapshotterGetter, -) (pkgrestore.Result, pkgrestore.Result) { +) (results.Result, results.Result) { res := r.Called(info.Log, info.Restore, info.Backup, info.BackupReader, actions) r.calledWithArg = *info.Restore - return res.Get(0).(pkgrestore.Result), res.Get(1).(pkgrestore.Result) + return res.Get(0).(results.Result), res.Get(1).(results.Result) } func (r *fakeRestorer) RestoreWithResolvers(req pkgrestore.Request, @@ -877,11 +878,11 @@ func (r *fakeRestorer) RestoreWithResolvers(req pkgrestore.Request, itemSnapshotterResolver framework.ItemSnapshotterResolver, snapshotLocationLister listers.VolumeSnapshotLocationLister, volumeSnapshotterGetter pkgrestore.VolumeSnapshotterGetter, -) (pkgrestore.Result, pkgrestore.Result) { +) (results.Result, results.Result) { res := r.Called(req.Log, req.Restore, req.Backup, req.BackupReader, resolver, itemSnapshotterResolver, snapshotLocationLister, volumeSnapshotterGetter) r.calledWithArg = *req.Restore - return res.Get(0).(pkgrestore.Result), res.Get(1).(pkgrestore.Result) + return res.Get(0).(results.Result), res.Get(1).(results.Result) } diff --git a/pkg/generated/clientset/versioned/fake/register.go b/pkg/generated/clientset/versioned/fake/register.go index 3482b9c353..5839b1517a 100644 --- a/pkg/generated/clientset/versioned/fake/register.go +++ b/pkg/generated/clientset/versioned/fake/register.go @@ -37,14 +37,14 @@ var localSchemeBuilder = runtime.SchemeBuilder{ // AddToScheme adds all types of this clientset into the given scheme. This allows composition // of clientsets, like in: // -// import ( -// "k8s.io/client-go/kubernetes" -// clientsetscheme "k8s.io/client-go/kubernetes/scheme" -// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" -// ) +// import ( +// "k8s.io/client-go/kubernetes" +// clientsetscheme "k8s.io/client-go/kubernetes/scheme" +// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +// ) // -// kclientset, _ := kubernetes.NewForConfig(c) -// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) +// kclientset, _ := kubernetes.NewForConfig(c) +// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) // // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types // correctly. diff --git a/pkg/generated/clientset/versioned/scheme/register.go b/pkg/generated/clientset/versioned/scheme/register.go index 6eadbc890c..f94b9a72c3 100644 --- a/pkg/generated/clientset/versioned/scheme/register.go +++ b/pkg/generated/clientset/versioned/scheme/register.go @@ -37,14 +37,14 @@ var localSchemeBuilder = runtime.SchemeBuilder{ // AddToScheme adds all types of this clientset into the given scheme. This allows composition // of clientsets, like in: // -// import ( -// "k8s.io/client-go/kubernetes" -// clientsetscheme "k8s.io/client-go/kubernetes/scheme" -// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" -// ) +// import ( +// "k8s.io/client-go/kubernetes" +// clientsetscheme "k8s.io/client-go/kubernetes/scheme" +// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +// ) // -// kclientset, _ := kubernetes.NewForConfig(c) -// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) +// kclientset, _ := kubernetes.NewForConfig(c) +// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) // // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types // correctly. diff --git a/pkg/install/resources.go b/pkg/install/resources.go index ddcce46684..0a78531549 100644 --- a/pkg/install/resources.go +++ b/pkg/install/resources.go @@ -136,13 +136,18 @@ func ClusterRoleBinding(namespace string) *rbacv1.ClusterRoleBinding { } func Namespace(namespace string) *corev1.Namespace { - return &corev1.Namespace{ + ns := &corev1.Namespace{ ObjectMeta: objectMeta("", namespace), TypeMeta: metav1.TypeMeta{ Kind: "Namespace", APIVersion: corev1.SchemeGroupVersion.String(), }, } + + ns.Labels["pod-security.kubernetes.io/enforce"] = "privileged" + ns.Labels["pod-security.kubernetes.io/enforce-version"] = "latest" + + return ns } func BackupStorageLocation(namespace, provider, bucket, prefix string, config map[string]string, caCert []byte) *velerov1api.BackupStorageLocation { diff --git a/pkg/install/resources_test.go b/pkg/install/resources_test.go index 748d70defe..298dca9eb7 100644 --- a/pkg/install/resources_test.go +++ b/pkg/install/resources_test.go @@ -40,6 +40,11 @@ func TestResources(t *testing.T) { ns := Namespace("velero") assert.Equal(t, "velero", ns.Name) + // For k8s version v1.25 and later, need to add the following labels to make + // velero installation namespace has privileged version to work with + // PSA(Pod Security Admission) and PSS(Pod Security Standards). + assert.Equal(t, ns.Labels["pod-security.kubernetes.io/enforce"], "privileged") + assert.Equal(t, ns.Labels["pod-security.kubernetes.io/enforce-version"], "latest") crb := ClusterRoleBinding(DefaultVeleroNamespace) // The CRB is a cluster-scoped resource diff --git a/pkg/persistence/object_store.go b/pkg/persistence/object_store.go index acda15323a..dc560cd536 100644 --- a/pkg/persistence/object_store.go +++ b/pkg/persistence/object_store.go @@ -42,6 +42,7 @@ type BackupInfo struct { Metadata, Contents, Log, + BackupResults, PodVolumeBackups, VolumeSnapshots, ItemSnapshots, @@ -261,6 +262,7 @@ func (s *objectBackupStore) PutBackup(info BackupInfo) error { s.layout.getCSIVolumeSnapshotKey(info.Name): info.CSIVolumeSnapshots, s.layout.getCSIVolumeSnapshotContentsKey(info.Name): info.CSIVolumeSnapshotContents, s.layout.getCSIVolumeSnapshotClassesKey(info.Name): info.CSIVolumeSnapshotClasses, + s.layout.getBackupResultsKey(info.Name): info.BackupResults, } for key, reader := range backupObjs { diff --git a/pkg/persistence/object_store_layout.go b/pkg/persistence/object_store_layout.go index 7042c40b37..12685b15cb 100644 --- a/pkg/persistence/object_store_layout.go +++ b/pkg/persistence/object_store_layout.go @@ -116,3 +116,7 @@ func (l *ObjectStoreLayout) getCSIVolumeSnapshotContentsKey(backup string) strin func (l *ObjectStoreLayout) getCSIVolumeSnapshotClassesKey(backup string) string { return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-csi-volumesnapshotclasses.json.gz", backup)) } + +func (l *ObjectStoreLayout) getBackupResultsKey(backup string) string { + return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-results.gz", backup)) +} diff --git a/pkg/plugin/clientmgmt/process/logrus_adapter_test.go b/pkg/plugin/clientmgmt/process/logrus_adapter_test.go index dae7f8ce2f..76fbc4f1c6 100644 --- a/pkg/plugin/clientmgmt/process/logrus_adapter_test.go +++ b/pkg/plugin/clientmgmt/process/logrus_adapter_test.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/plugin/clientmgmt/process/process_test.go b/pkg/plugin/clientmgmt/process/process_test.go index ce7c7c07b9..e65c890511 100644 --- a/pkg/plugin/clientmgmt/process/process_test.go +++ b/pkg/plugin/clientmgmt/process/process_test.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/plugin/clientmgmt/process/registry_test.go b/pkg/plugin/clientmgmt/process/registry_test.go index cd0614f857..c441fa519b 100644 --- a/pkg/plugin/clientmgmt/process/registry_test.go +++ b/pkg/plugin/clientmgmt/process/registry_test.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/plugin/framework/common/client_dispenser_test.go b/pkg/plugin/framework/common/client_dispenser_test.go index bd1461658a..3a33774987 100644 --- a/pkg/plugin/framework/common/client_dispenser_test.go +++ b/pkg/plugin/framework/common/client_dispenser_test.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/plugin/framework/common/plugin_base_test.go b/pkg/plugin/framework/common/plugin_base_test.go index 04e066d0c6..e9d5ec2e2b 100644 --- a/pkg/plugin/framework/common/plugin_base_test.go +++ b/pkg/plugin/framework/common/plugin_base_test.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/plugin/framework/item_snapshotter_server.go b/pkg/plugin/framework/item_snapshotter_server.go index 21ab92110f..88de162b98 100644 --- a/pkg/plugin/framework/item_snapshotter_server.go +++ b/pkg/plugin/framework/item_snapshotter_server.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/plugin/framework/logger_test.go b/pkg/plugin/framework/logger_test.go index 15564646d8..7e9ba7bf2f 100644 --- a/pkg/plugin/framework/logger_test.go +++ b/pkg/plugin/framework/logger_test.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/podvolume/backupper.go b/pkg/podvolume/backupper.go index 2bd1a0b1c4..4def12ba67 100644 --- a/pkg/podvolume/backupper.go +++ b/pkg/podvolume/backupper.go @@ -115,23 +115,28 @@ func (b *backupper) BackupPodVolumes(backup *velerov1api.Backup, pod *corev1api. return nil, nil } - repositoryType := getRepositoryType(b.uploaderType) - if repositoryType == "" { - err := errors.Errorf("empty repository type, uploader %s", b.uploaderType) - return nil, []error{err} + err := kube.IsPodRunning(pod) + if err != nil { + for _, volumeName := range volumesToBackup { + err = errors.Wrapf(err, "backup for volume %s is skipped", volumeName) + log.WithError(err).Warn("Skip pod volume") + } + + return nil, nil } - repo, err := b.repoEnsurer.EnsureRepo(b.ctx, backup.Namespace, pod.Namespace, backup.Spec.StorageLocation, repositoryType) + err = nodeagent.IsRunningInNode(b.ctx, backup.Namespace, pod.Spec.NodeName, b.podClient) if err != nil { return nil, []error{err} } - err = kube.IsPodRunning(pod) - if err != nil { + repositoryType := getRepositoryType(b.uploaderType) + if repositoryType == "" { + err := errors.Errorf("empty repository type, uploader %s", b.uploaderType) return nil, []error{err} } - err = nodeagent.IsRunningInNode(b.ctx, backup.Namespace, pod.Spec.NodeName, b.podClient) + repo, err := b.repoEnsurer.EnsureRepo(b.ctx, backup.Namespace, pod.Namespace, backup.Spec.StorageLocation, repositoryType) if err != nil { return nil, []error{err} } diff --git a/pkg/repository/manager.go b/pkg/repository/manager.go index fae6877dc4..c8cabdd814 100644 --- a/pkg/repository/manager.go +++ b/pkg/repository/manager.go @@ -172,6 +172,11 @@ func (m *manager) PruneRepo(repo *velerov1api.BackupRepository) error { if err != nil { return errors.WithStack(err) } + + if err := prd.BoostRepoConnect(context.Background(), param); err != nil { + return errors.WithStack(err) + } + return prd.PruneRepo(context.Background(), param) } @@ -207,6 +212,11 @@ func (m *manager) Forget(ctx context.Context, snapshot SnapshotIdentifier) error if err != nil { return errors.WithStack(err) } + + if err := prd.BoostRepoConnect(context.Background(), param); err != nil { + return errors.WithStack(err) + } + return prd.Forget(context.Background(), snapshot.SnapshotID, param) } diff --git a/pkg/repository/provider/provider.go b/pkg/repository/provider/provider.go index 6579386d6e..4b76830f58 100644 --- a/pkg/repository/provider/provider.go +++ b/pkg/repository/provider/provider.go @@ -43,6 +43,11 @@ type Provider interface { // is already initialized, or do nothing if the repository is already connected PrepareRepo(ctx context.Context, param RepoParam) error + // BoostRepoConnect is used to re-ensure the local connection to the repo, + // so that the followed operations could succeed in some environment reset + // scenarios, for example, pod restart + BoostRepoConnect(ctx context.Context, param RepoParam) error + // PruneRepo does a full prune/maintenance of the repository PruneRepo(ctx context.Context, param RepoParam) error diff --git a/pkg/repository/provider/restic.go b/pkg/repository/provider/restic.go index 65038f0f22..a77c950bea 100644 --- a/pkg/repository/provider/restic.go +++ b/pkg/repository/provider/restic.go @@ -62,6 +62,10 @@ func (r *resticRepositoryProvider) PrepareRepo(ctx context.Context, param RepoPa return nil } +func (r *resticRepositoryProvider) BoostRepoConnect(ctx context.Context, param RepoParam) error { + return nil +} + func (r *resticRepositoryProvider) PruneRepo(ctx context.Context, param RepoParam) error { return r.svc.PruneRepo(param.BackupLocation, param.BackupRepo) } diff --git a/pkg/repository/provider/unified_repo.go b/pkg/repository/provider/unified_repo.go index 2ba1c7c0d1..9161e06210 100644 --- a/pkg/repository/provider/unified_repo.go +++ b/pkg/repository/provider/unified_repo.go @@ -200,6 +200,37 @@ func (urp *unifiedRepoProvider) PrepareRepo(ctx context.Context, param RepoParam return nil } +func (urp *unifiedRepoProvider) BoostRepoConnect(ctx context.Context, param RepoParam) error { + log := urp.log.WithFields(logrus.Fields{ + "BSL name": param.BackupLocation.Name, + "repo name": param.BackupRepo.Name, + "repo UID": param.BackupRepo.UID, + }) + + log.Debug("Start to boost repo connect") + + repoOption, err := udmrepo.NewRepoOptions( + udmrepo.WithPassword(urp, param), + udmrepo.WithConfigFile(urp.workPath, string(param.BackupRepo.UID)), + udmrepo.WithDescription(repoConnectDesc), + ) + + if err != nil { + return errors.Wrap(err, "error to get repo options") + } + + bkRepo, err := urp.repoService.Open(ctx, *repoOption) + if err == nil { + if c := bkRepo.Close(ctx); c != nil { + log.WithError(c).Error("Failed to close repo") + } + + return nil + } + + return urp.ConnectToRepo(ctx, param) +} + func (urp *unifiedRepoProvider) PruneRepo(ctx context.Context, param RepoParam) error { log := urp.log.WithFields(logrus.Fields{ "BSL name": param.BackupLocation.Name, diff --git a/pkg/restic/command_factory_test.go b/pkg/restic/command_factory_test.go index 4a38bc1901..c0a627ad1f 100644 --- a/pkg/restic/command_factory_test.go +++ b/pkg/restic/command_factory_test.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/restore/change_pvc_node_selector.go b/pkg/restore/change_pvc_node_selector.go index d281d318f7..774903d372 100644 --- a/pkg/restore/change_pvc_node_selector.go +++ b/pkg/restore/change_pvc_node_selector.go @@ -5,7 +5,7 @@ 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 +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, @@ -59,8 +59,9 @@ func (p *ChangePVCNodeSelectorAction) AppliesTo() (velero.ResourceSelector, erro } // Execute updates the pvc's selected-node annotation: -// a) if node mapping found in the config map for the plugin -// b) if node mentioned in annotation doesn't exist +// +// a) if node mapping found in the config map for the plugin +// b) if node mentioned in annotation doesn't exist func (p *ChangePVCNodeSelectorAction) Execute(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) { p.logger.Info("Executing ChangePVCNodeSelectorAction") defer p.logger.Info("Done executing ChangePVCNodeSelectorAction") diff --git a/pkg/restore/merge_service_account.go b/pkg/restore/merge_service_account.go index dd210f10f0..94564e1696 100644 --- a/pkg/restore/merge_service_account.go +++ b/pkg/restore/merge_service_account.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/restore/merge_service_account_test.go b/pkg/restore/merge_service_account_test.go index 8079018ee3..e58c423744 100644 --- a/pkg/restore/merge_service_account_test.go +++ b/pkg/restore/merge_service_account_test.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/restore/prioritize_group_version.go b/pkg/restore/prioritize_group_version.go index d4ad9331c5..e3a01c03db 100644 --- a/pkg/restore/prioritize_group_version.go +++ b/pkg/restore/prioritize_group_version.go @@ -214,7 +214,7 @@ func userPriorityConfigMap() (*corev1.ConfigMap, error) { return nil, errors.Wrap(err, "reading client config file") } - fc := client.NewFactory("APIGroupVersionsRestore", "", cfg) + fc := client.NewFactory("APIGroupVersionsRestore", cfg) kc, err := fc.KubeClient() if err != nil { diff --git a/pkg/restore/priority.go b/pkg/restore/priority.go index c544532c74..d84a95dd75 100644 --- a/pkg/restore/priority.go +++ b/pkg/restore/priority.go @@ -26,9 +26,10 @@ const ( ) // Priorities defines the desired order of resource operations: -// Resources in the HighPriorities list will be handled first -// Resources in the LowPriorities list will be handled last -// Other resources will be handled alphabetically after the high prioritized resources and before the low prioritized resources +// +// Resources in the HighPriorities list will be handled first +// Resources in the LowPriorities list will be handled last +// Other resources will be handled alphabetically after the high prioritized resources and before the low prioritized resources type Priorities struct { HighPriorities []string LowPriorities []string diff --git a/pkg/restore/restore.go b/pkg/restore/restore.go index bea58fafc4..00ee6ec9a5 100644 --- a/pkg/restore/restore.go +++ b/pkg/restore/restore.go @@ -67,6 +67,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/util/collections" "github.com/vmware-tanzu/velero/pkg/util/filesystem" "github.com/vmware-tanzu/velero/pkg/util/kube" + . "github.com/vmware-tanzu/velero/pkg/util/results" "github.com/vmware-tanzu/velero/pkg/volume" ) @@ -1421,6 +1422,27 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso } } + // restore the managedFields + withoutManagedFields := createdObj.DeepCopy() + createdObj.SetManagedFields(obj.GetManagedFields()) + patchBytes, err := generatePatch(withoutManagedFields, createdObj) + if err != nil { + ctx.log.Errorf("error generating patch for managed fields %s: %v", kube.NamespaceAndName(obj), err) + errs.Add(namespace, err) + return warnings, errs + } + if patchBytes != nil { + if _, err = resourceClient.Patch(name, patchBytes); err != nil { + ctx.log.Errorf("error patch for managed fields %s: %v", kube.NamespaceAndName(obj), err) + if !apierrors.IsNotFound(err) { + errs.Add(namespace, err) + return warnings, errs + } + } else { + ctx.log.Infof("the managed fields for %s is patched", kube.NamespaceAndName(obj)) + } + } + if groupResource == kuberesource.Pods { pod := new(v1.Pod) if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), pod); err != nil { @@ -1720,8 +1742,8 @@ func resetMetadata(obj *unstructured.Unstructured) (*unstructured.Unstructured, for k := range metadata { switch k { - case "name", "namespace", "labels", "annotations": - default: + case "generateName", "selfLink", "uid", "resourceVersion", "generation", "creationTimestamp", "deletionTimestamp", + "deletionGracePeriodSeconds", "ownerReferences": delete(metadata, k) } } diff --git a/pkg/restore/restore_test.go b/pkg/restore/restore_test.go index c8bd37ad50..90a0048711 100644 --- a/pkg/restore/restore_test.go +++ b/pkg/restore/restore_test.go @@ -56,6 +56,7 @@ import ( testutil "github.com/vmware-tanzu/velero/pkg/test" "github.com/vmware-tanzu/velero/pkg/util/kube" kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube" + . "github.com/vmware-tanzu/velero/pkg/util/results" "github.com/vmware-tanzu/velero/pkg/volume" ) @@ -867,7 +868,7 @@ func TestRestoreItems(t *testing.T) { want []*test.APIResource }{ { - name: "metadata other than namespace/name/labels/annotations gets removed", + name: "metadata uid/resourceVersion/etc. gets removed", restore: defaultRestore().Result(), backup: defaultBackup().Result(), tarball: test.NewTarWriter(t). @@ -877,6 +878,7 @@ func TestRestoreItems(t *testing.T) { builder.WithLabels("key-1", "val-1"), builder.WithAnnotations("key-1", "val-1"), builder.WithFinalizers("finalizer-1"), + builder.WithUID("uid"), ). Result(), ). @@ -890,6 +892,7 @@ func TestRestoreItems(t *testing.T) { ObjectMeta( builder.WithLabels("key-1", "val-1", "velero.io/backup-name", "backup-1", "velero.io/restore-name", "restore-1"), builder.WithAnnotations("key-1", "val-1"), + builder.WithFinalizers("finalizer-1"), ). Result(), ), @@ -1107,6 +1110,53 @@ func TestRestoreItems(t *testing.T) { }), }, }, + { + name: "metadata managedFields gets restored", + restore: defaultRestore().Result(), + backup: defaultBackup().Result(), + tarball: test.NewTarWriter(t). + AddItems("pods", + builder.ForPod("ns-1", "pod-1"). + ObjectMeta( + builder.WithManagedFields([]metav1.ManagedFieldsEntry{ + { + Manager: "kubectl", + Operation: "Apply", + APIVersion: "v1", + FieldsType: "FieldsV1", + FieldsV1: &metav1.FieldsV1{ + Raw: []byte(`{"f:data": {"f:key":{}}}`), + }, + }, + }), + ). + Result(), + ). + Done(), + apiResources: []*test.APIResource{ + test.Pods(), + }, + want: []*test.APIResource{ + test.Pods( + builder.ForPod("ns-1", "pod-1"). + ObjectMeta( + builder.WithLabels("velero.io/backup-name", "backup-1", "velero.io/restore-name", "restore-1"), + builder.WithManagedFields([]metav1.ManagedFieldsEntry{ + { + Manager: "kubectl", + Operation: "Apply", + APIVersion: "v1", + FieldsType: "FieldsV1", + FieldsV1: &metav1.FieldsV1{ + Raw: []byte(`{"f:data": {"f:key":{}}}`), + }, + }, + }), + ). + Result(), + ), + }, + }, } for _, tc := range tests { @@ -2823,10 +2873,16 @@ func TestResetMetadata(t *testing.T) { expectedErr: true, }, { - name: "keep name, namespace, labels, annotations only", - obj: NewTestUnstructured().WithMetadata("name", "blah", "namespace", "labels", "annotations", "foo").Unstructured, + name: "keep name, namespace, labels, annotations, managedFields, finalizers", + obj: NewTestUnstructured().WithMetadata("name", "namespace", "labels", "annotations", "managedFields", "finalizers").Unstructured, + expectedErr: false, + expectedRes: NewTestUnstructured().WithMetadata("name", "namespace", "labels", "annotations", "managedFields", "finalizers").Unstructured, + }, + { + name: "remove uid, ownerReferences", + obj: NewTestUnstructured().WithMetadata("name", "namespace", "uid", "ownerReferences").Unstructured, expectedErr: false, - expectedRes: NewTestUnstructured().WithMetadata("name", "namespace", "labels", "annotations").Unstructured, + expectedRes: NewTestUnstructured().WithMetadata("name", "namespace").Unstructured, }, { name: "keep status", diff --git a/pkg/restore/secret_action.go b/pkg/restore/secret_action.go new file mode 100644 index 0000000000..1e63372b65 --- /dev/null +++ b/pkg/restore/secret_action.go @@ -0,0 +1,107 @@ +/* +Copyright 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 restore + +import ( + "context" + "fmt" + "strings" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/vmware-tanzu/velero/pkg/plugin/velero" + "github.com/vmware-tanzu/velero/pkg/util/kube" +) + +// SecretAction is a restore item action for secrets +type SecretAction struct { + logger logrus.FieldLogger + client client.Client +} + +// NewSecretAction creates a new SecretAction instance +func NewSecretAction(logger logrus.FieldLogger, client client.Client) *SecretAction { + return &SecretAction{ + logger: logger, + client: client, + } +} + +// AppliesTo indicates which resources this action applies +func (s *SecretAction) AppliesTo() (velero.ResourceSelector, error) { + return velero.ResourceSelector{ + IncludedResources: []string{"secrets"}, + }, nil +} + +// Execute the action +func (s *SecretAction) Execute(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) { + s.logger.Info("Executing SecretAction") + defer s.logger.Info("Done executing SecretAction") + + var secret corev1.Secret + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(input.Item.UnstructuredContent(), &secret); err != nil { + return nil, errors.Wrap(err, "unable to convert secret from runtime.Unstructured") + } + + log := s.logger.WithField("secret", kube.NamespaceAndName(&secret)) + if secret.Type != corev1.SecretTypeServiceAccountToken { + log.Debug("No match found - including this secret") + return &velero.RestoreItemActionExecuteOutput{ + UpdatedItem: input.Item, + }, nil + } + + // The auto created service account token secret will be created by kube controller automatically again(before Kubernetes v1.22), no need to restore. + // This will cause the patch operation of managedFields failed if we restore it as the secret is removed immediately + // after restoration and the patch operation reports not found error. + list := &corev1.ServiceAccountList{} + if err := s.client.List(context.Background(), list, &client.ListOptions{Namespace: secret.Namespace}); err != nil { + return nil, errors.Wrap(err, "unable to list the service accounts") + } + for _, sa := range list.Items { + if strings.HasPrefix(secret.Name, fmt.Sprintf("%s-token-", sa.Name)) { + log.Debug("auto created service account token secret found - excluding this secret") + return &velero.RestoreItemActionExecuteOutput{ + UpdatedItem: input.Item, + SkipRestore: true, + }, nil + } + } + + log.Debug("service account token secret(not auto created) found - remove some fields from this secret") + // If the annotation and data are not removed, the secret cannot be restored successfully. + // The kube controller will fill the annotation and data with new value automatically: + // https://kubernetes.io/docs/concepts/configuration/secret/#service-account-token-secrets + delete(secret.Annotations, "kubernetes.io/service-account.uid") + delete(secret.Data, "token") + delete(secret.Data, "ca.crt") + + res, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&secret) + if err != nil { + return nil, errors.Wrap(err, "unable to convert secret to runtime.Unstructured") + } + + return &velero.RestoreItemActionExecuteOutput{ + UpdatedItem: &unstructured.Unstructured{Object: res}, + }, nil +} diff --git a/pkg/restore/secret_action_test.go b/pkg/restore/secret_action_test.go new file mode 100644 index 0000000000..a627e5296f --- /dev/null +++ b/pkg/restore/secret_action_test.go @@ -0,0 +1,142 @@ +/* +Copyright 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 restore + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/vmware-tanzu/velero/pkg/plugin/velero" + "github.com/vmware-tanzu/velero/pkg/test" +) + +func TestSecretActionAppliesTo(t *testing.T) { + action := NewSecretAction(test.NewLogger(), nil) + actual, err := action.AppliesTo() + require.NoError(t, err) + assert.Equal(t, velero.ResourceSelector{IncludedResources: []string{"secrets"}}, actual) +} + +func TestSecretActionExecute(t *testing.T) { + tests := []struct { + name string + input *corev1.Secret + serviceAccount *corev1.ServiceAccount + skipped bool + output *corev1.Secret + }{ + { + name: "not service account token secret", + input: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "foo", + Name: "default-token-sfafa", + }, + Type: corev1.SecretTypeOpaque, + }, + skipped: false, + output: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "foo", + Name: "default-token-sfafa", + }, + Type: corev1.SecretTypeOpaque, + }, + }, + { + name: "auto created service account token", + input: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "foo", + Name: "default-token-sfafa", + }, + Type: corev1.SecretTypeServiceAccountToken, + }, + serviceAccount: &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "foo", + Name: "default", + }, + }, + skipped: true, + }, + { + name: "not auto created service account token", + input: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "foo", + Name: "my-token", + Annotations: map[string]string{ + "kubernetes.io/service-account.uid": "uid", + "key": "value", + }, + }, + Type: corev1.SecretTypeServiceAccountToken, + Data: map[string][]byte{ + "token": []byte("token"), + "ca.crt": []byte("ca"), + "key": []byte("value"), + }, + }, + skipped: false, + output: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "foo", + Name: "my-token", + Annotations: map[string]string{ + "key": "value", + }, + }, + Type: corev1.SecretTypeServiceAccountToken, + Data: map[string][]byte{ + "key": []byte("value"), + }, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + secretUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.input) + require.NoError(t, err) + var serviceAccounts []client.Object + if tc.serviceAccount != nil { + serviceAccounts = append(serviceAccounts, tc.serviceAccount) + } + client := fake.NewClientBuilder().WithObjects(serviceAccounts...).Build() + action := NewSecretAction(test.NewLogger(), client) + res, err := action.Execute(&velero.RestoreItemActionExecuteInput{ + Item: &unstructured.Unstructured{Object: secretUnstructured}, + }) + require.NoError(t, err) + assert.Equal(t, tc.skipped, res.SkipRestore) + if !tc.skipped { + r, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.output) + require.NoError(t, err) + assert.EqualValues(t, &unstructured.Unstructured{Object: r}, res.UpdatedItem) + } + }) + } +} diff --git a/pkg/restore/service_action.go b/pkg/restore/service_action.go index 0b22cf0959..68daf5404f 100644 --- a/pkg/restore/service_action.go +++ b/pkg/restore/service_action.go @@ -18,6 +18,7 @@ package restore import ( "encoding/json" + "fmt" "strconv" "github.com/pkg/errors" @@ -83,10 +84,10 @@ func deleteNodePorts(service *corev1api.Service) error { // find any NodePorts whose values were explicitly specified according // to the last-applied-config annotation. We'll retain these values, and // clear out any other (presumably auto-assigned) NodePort values. - explicitNodePorts := sets.NewString() - unnamedPortInts := sets.NewInt() lastAppliedConfig, ok := service.Annotations[annotationLastAppliedConfig] if ok { + explicitNodePorts := sets.NewString() + unnamedPortInts := sets.NewInt() appliedServiceUnstructured := new(map[string]interface{}) if err := json.Unmarshal([]byte(lastAppliedConfig), appliedServiceUnstructured); err != nil { return errors.WithStack(err) @@ -134,19 +135,58 @@ func deleteNodePorts(service *corev1api.Service) error { } } } + + for i, port := range service.Spec.Ports { + if port.Name != "" { + if !explicitNodePorts.Has(port.Name) { + service.Spec.Ports[i].NodePort = 0 + } + } else { + if !unnamedPortInts.Has(int(port.NodePort)) { + service.Spec.Ports[i].NodePort = 0 + } + } + } + + return nil } - for i, port := range service.Spec.Ports { - if port.Name != "" { - if !explicitNodePorts.Has(port.Name) { - service.Spec.Ports[i].NodePort = 0 + explicitNodePorts := sets.NewString() + for _, entry := range service.GetManagedFields() { + if entry.FieldsV1 == nil { + continue + } + fields := new(map[string]interface{}) + if err := json.Unmarshal(entry.FieldsV1.Raw, fields); err != nil { + return errors.WithStack(err) + } + + ports, exist, err := unstructured.NestedMap(*fields, "f:spec", "f:ports") + if err != nil { + return errors.WithStack(err) + } + if !exist { + continue + } + for key, port := range ports { + p, ok := port.(map[string]interface{}) + if !ok { + continue } - } else { - if !unnamedPortInts.Has(int(port.NodePort)) { - service.Spec.Ports[i].NodePort = 0 + if _, exist := p["f:nodePort"]; exist { + explicitNodePorts.Insert(key) } } } - + for i, port := range service.Spec.Ports { + k := portKey(port) + if !explicitNodePorts.Has(k) { + service.Spec.Ports[i].NodePort = 0 + } + } return nil } + +func portKey(port corev1api.ServicePort) string { + return fmt.Sprintf(`k:{"port":%d,"protocol":"%s"}`, port.Port, port.Protocol) +} diff --git a/pkg/restore/service_action_test.go b/pkg/restore/service_action_test.go index 59fef00a15..d80cc1c450 100644 --- a/pkg/restore/service_action_test.go +++ b/pkg/restore/service_action_test.go @@ -368,6 +368,124 @@ func TestServiceActionExecute(t *testing.T) { }, }, }, + { + name: "nodePort should be delete when not specified in managedFields", + obj: corev1api.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "svc-1", + ManagedFields: []metav1.ManagedFieldsEntry{ + { + FieldsV1: &metav1.FieldsV1{ + Raw: []byte(`{"f:spec":{"f:ports":{"k:{\"port\":443,\"protocol\":\"TCP\"}":{".":{},"f:name":{},"f:port":{}},"k:{\"port\":80,\"protocol\":\"TCP\"}":{".":{},"f:name":{},"f:port":{}}},"f:selector":{},"f:type":{}}}`), + }, + }, + }, + }, + Spec: corev1api.ServiceSpec{ + Ports: []corev1api.ServicePort{ + { + Name: "http", + Port: 80, + Protocol: "TCP", + }, + { + Name: "https", + Port: 443, + Protocol: "TCP", + }, + }, + }, + }, + restore: builder.ForRestore(api.DefaultNamespace, "").Result(), + expectedRes: corev1api.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "svc-1", + ManagedFields: []metav1.ManagedFieldsEntry{ + { + FieldsV1: &metav1.FieldsV1{ + Raw: []byte(`{"f:spec":{"f:ports":{"k:{\"port\":443,\"protocol\":\"TCP\"}":{".":{},"f:name":{},"f:port":{}},"k:{\"port\":80,\"protocol\":\"TCP\"}":{".":{},"f:name":{},"f:port":{}}},"f:selector":{},"f:type":{}}}`), + }, + }, + }, + }, + Spec: corev1api.ServiceSpec{ + Ports: []corev1api.ServicePort{ + { + Name: "http", + Port: 80, + NodePort: 0, + Protocol: "TCP", + }, + { + Name: "https", + Port: 443, + NodePort: 0, + Protocol: "TCP", + }, + }, + }, + }, + }, + { + name: "nodePort should be preserved when specified in managedFields", + obj: corev1api.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "svc-1", + ManagedFields: []metav1.ManagedFieldsEntry{ + { + FieldsV1: &metav1.FieldsV1{ + Raw: []byte(`{"f:spec":{"f:ports":{"k:{\"port\":443,\"protocol\":\"TCP\"}":{".":{},"f:name":{},"f:nodePort":{},"f:port":{}},"k:{\"port\":80,\"protocol\":\"TCP\"}":{".":{},"f:name":{},"f:nodePort":{},"f:port":{}}},"f:selector":{},"f:type":{}}}`), + }, + }, + }, + }, + Spec: corev1api.ServiceSpec{ + Ports: []corev1api.ServicePort{ + { + Name: "http", + Port: 80, + NodePort: 30000, + Protocol: "TCP", + }, + { + Name: "https", + Port: 443, + NodePort: 30002, + Protocol: "TCP", + }, + }, + }, + }, + restore: builder.ForRestore(api.DefaultNamespace, "").Result(), + expectedRes: corev1api.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "svc-1", + ManagedFields: []metav1.ManagedFieldsEntry{ + { + FieldsV1: &metav1.FieldsV1{ + Raw: []byte(`{"f:spec":{"f:ports":{"k:{\"port\":443,\"protocol\":\"TCP\"}":{".":{},"f:name":{},"f:nodePort":{},"f:port":{}},"k:{\"port\":80,\"protocol\":\"TCP\"}":{".":{},"f:name":{},"f:nodePort":{},"f:port":{}}},"f:selector":{},"f:type":{}}}`), + }, + }, + }, + }, + Spec: corev1api.ServiceSpec{ + Ports: []corev1api.ServicePort{ + { + Name: "http", + Port: 80, + NodePort: 30000, + Protocol: "TCP", + }, + { + Name: "https", + Port: 443, + NodePort: 30002, + Protocol: "TCP", + }, + }, + }, + }, + }, } for _, test := range tests { diff --git a/pkg/test/helpers.go b/pkg/test/helpers.go index 261e33da4e..5c103c5eae 100644 --- a/pkg/test/helpers.go +++ b/pkg/test/helpers.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/test/mock_pod_command_executor.go b/pkg/test/mock_pod_command_executor.go index ef28ec96e1..219361b776 100644 --- a/pkg/test/mock_pod_command_executor.go +++ b/pkg/test/mock_pod_command_executor.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/uploader/kopia/progress.go b/pkg/uploader/kopia/progress.go index d768181269..515d060484 100644 --- a/pkg/uploader/kopia/progress.go +++ b/pkg/uploader/kopia/progress.go @@ -5,7 +5,7 @@ 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 +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, @@ -20,10 +20,12 @@ import ( "sync/atomic" "time" + "github.com/sirupsen/logrus" + "github.com/vmware-tanzu/velero/pkg/uploader" ) -//Throttle throttles controlle the interval of output result +// Throttle throttles controlle the interval of output result type Throttle struct { throttle int64 interval time.Duration @@ -63,9 +65,10 @@ type KopiaProgress struct { processedBytes int64 // which statistic all bytes has been processed currently outputThrottle Throttle // which control the frequency of update progress Updater uploader.ProgressUpdater //which kopia progress will call the UpdateProgress interface, the third party will implement the interface to do the progress update + Log logrus.FieldLogger // output info into log when backup } -//UploadedBytes the total bytes has uploaded currently +// UploadedBytes the total bytes has uploaded currently func (p *KopiaProgress) UploadedBytes(numBytes int64) { atomic.AddInt64(&p.uploadedBytes, numBytes) atomic.AddInt32(&p.uploadedFiles, 1) @@ -73,16 +76,18 @@ func (p *KopiaProgress) UploadedBytes(numBytes int64) { p.UpdateProgress() } -//Error statistic the total Error has occurred +// Error statistic the total Error has occurred func (p *KopiaProgress) Error(path string, err error, isIgnored bool) { if isIgnored { atomic.AddInt32(&p.ignoredErrorCount, 1) + p.Log.Warnf("Ignored error when processing %v: %v", path, err) } else { atomic.AddInt32(&p.fatalErrorCount, 1) + p.Log.Errorf("Error when processing %v: %v", path, err) } } -//EstimatedDataSize statistic the total size of files to be processed and total files to be processed +// EstimatedDataSize statistic the total size of files to be processed and total files to be processed func (p *KopiaProgress) EstimatedDataSize(fileCount int, totalBytes int64) { atomic.StoreInt64(&p.estimatedTotalBytes, totalBytes) atomic.StoreInt32(&p.estimatedFileCount, int32(fileCount)) @@ -90,57 +95,57 @@ func (p *KopiaProgress) EstimatedDataSize(fileCount int, totalBytes int64) { p.UpdateProgress() } -//UpdateProgress which calls Updater UpdateProgress interface, update progress by third-party implementation +// UpdateProgress which calls Updater UpdateProgress interface, update progress by third-party implementation func (p *KopiaProgress) UpdateProgress() { if p.outputThrottle.ShouldOutput() { p.Updater.UpdateProgress(&uploader.UploaderProgress{TotalBytes: p.estimatedTotalBytes, BytesDone: p.processedBytes}) } } -//UploadStarted statistic the total Error has occurred +// UploadStarted statistic the total Error has occurred func (p *KopiaProgress) UploadStarted() {} -//CachedFile statistic the total bytes been cached currently +// CachedFile statistic the total bytes been cached currently func (p *KopiaProgress) CachedFile(fname string, numBytes int64) { atomic.AddInt64(&p.cachedBytes, numBytes) p.UpdateProgress() } -//HashedBytes statistic the total bytes been hashed currently +// HashedBytes statistic the total bytes been hashed currently func (p *KopiaProgress) HashedBytes(numBytes int64) { atomic.AddInt64(&p.processedBytes, numBytes) atomic.AddInt64(&p.hashededBytes, numBytes) p.UpdateProgress() } -//HashingFile statistic the file been hashed currently +// HashingFile statistic the file been hashed currently func (p *KopiaProgress) HashingFile(fname string) {} -//ExcludedFile statistic the file been excluded currently +// ExcludedFile statistic the file been excluded currently func (p *KopiaProgress) ExcludedFile(fname string, numBytes int64) {} -//ExcludedDir statistic the dir been excluded currently +// ExcludedDir statistic the dir been excluded currently func (p *KopiaProgress) ExcludedDir(dirname string) {} -//FinishedHashingFile which will called when specific file finished hash +// FinishedHashingFile which will called when specific file finished hash func (p *KopiaProgress) FinishedHashingFile(fname string, numBytes int64) { p.UpdateProgress() } -//StartedDirectory called when begin to upload one directory +// StartedDirectory called when begin to upload one directory func (p *KopiaProgress) StartedDirectory(dirname string) {} -//FinishedDirectory called when finish to upload one directory +// FinishedDirectory called when finish to upload one directory func (p *KopiaProgress) FinishedDirectory(dirname string) { p.UpdateProgress() } -//UploadFinished which report the files flushed after the Upload has completed. +// UploadFinished which report the files flushed after the Upload has completed. func (p *KopiaProgress) UploadFinished() { p.UpdateProgress() } -//ProgressBytes which statistic all bytes has been processed currently +// ProgressBytes which statistic all bytes has been processed currently func (p *KopiaProgress) ProgressBytes(processedBytes int64, totalBytes int64) { atomic.StoreInt64(&p.processedBytes, processedBytes) atomic.StoreInt64(&p.estimatedTotalBytes, totalBytes) diff --git a/pkg/uploader/kopia/shim.go b/pkg/uploader/kopia/shim.go index e8e2466d44..f8ddc9e4a3 100644 --- a/pkg/uploader/kopia/shim.go +++ b/pkg/uploader/kopia/shim.go @@ -5,7 +5,7 @@ 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 +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, @@ -31,18 +31,18 @@ import ( "github.com/kopia/kopia/repo/object" ) -//shimRepository which is one adapter for unifited repo and kopia. -//it implement kopia RepositoryWriter interfaces +// shimRepository which is one adapter for unifited repo and kopia. +// it implement kopia RepositoryWriter interfaces type shimRepository struct { udmRepo udmrepo.BackupRepo } -//shimObjectWriter object writer for unifited repo +// shimObjectWriter object writer for unifited repo type shimObjectWriter struct { repoWriter udmrepo.ObjectWriter } -//shimObjectReader object reader for unifited repo +// shimObjectReader object reader for unifited repo type shimObjectReader struct { repoReader udmrepo.ObjectReader } @@ -53,7 +53,7 @@ func NewShimRepo(repo udmrepo.BackupRepo) repo.RepositoryWriter { } } -//OpenObject open specific object +// OpenObject open specific object func (sr *shimRepository) OpenObject(ctx context.Context, id object.ID) (object.Reader, error) { reader, err := sr.udmRepo.OpenObject(ctx, udmrepo.ID(id)) if err != nil { @@ -94,7 +94,7 @@ func (sr *shimRepository) FindManifests(ctx context.Context, labels map[string]s } } -//GetKopiaManifestEntries get metadata from specific ManifestEntryMetadata +// GetKopiaManifestEntries get metadata from specific ManifestEntryMetadata func GetKopiaManifestEntry(uMani *udmrepo.ManifestEntryMetadata) *manifest.EntryMetadata { var ret manifest.EntryMetadata @@ -106,7 +106,7 @@ func GetKopiaManifestEntry(uMani *udmrepo.ManifestEntryMetadata) *manifest.Entry return &ret } -//GetKopiaManifestEntries get metadata list from specific ManifestEntryMetadata +// GetKopiaManifestEntries get metadata list from specific ManifestEntryMetadata func GetKopiaManifestEntries(uMani []*udmrepo.ManifestEntryMetadata) []*manifest.EntryMetadata { var ret []*manifest.EntryMetadata @@ -123,12 +123,12 @@ func GetKopiaManifestEntries(uMani []*udmrepo.ManifestEntryMetadata) []*manifest return ret } -//Time Get the local time of the unified repo +// Time Get the local time of the unified repo func (sr *shimRepository) Time() time.Time { return sr.udmRepo.Time() } -//ClientOptions is not supported by unified repo +// ClientOptions is not supported by unified repo func (sr *shimRepository) ClientOptions() repo.ClientOptions { return repo.ClientOptions{} } @@ -143,26 +143,26 @@ func (sr *shimRepository) ContentInfo(ctx context.Context, contentID content.ID) return nil, errors.New("not supported") } -//PrefetchContents is not supported by unified repo +// PrefetchContents is not supported by unified repo func (sr *shimRepository) PrefetchContents(ctx context.Context, contentIDs []content.ID, hint string) []content.ID { return nil } -//PrefetchObjects is not supported by unified repo +// PrefetchObjects is not supported by unified repo func (sr *shimRepository) PrefetchObjects(ctx context.Context, objectIDs []object.ID, hint string) ([]content.ID, error) { return nil, errors.New("not supported") } -//UpdateDescription is not supported by unified repo +// UpdateDescription is not supported by unified repo func (sr *shimRepository) UpdateDescription(d string) { } -//NewWriter is not supported by unified repo +// NewWriter is not supported by unified repo func (sr *shimRepository) NewWriter(ctx context.Context, option repo.WriteSessionOptions) (context.Context, repo.RepositoryWriter, error) { return nil, nil, errors.New("not supported") } -//Close will close unified repo +// Close will close unified repo func (sr *shimRepository) Close(ctx context.Context) error { return sr.udmRepo.Close(ctx) } @@ -222,7 +222,7 @@ func (sr *shimObjectReader) Seek(offset int64, whence int) (int64, error) { return sr.repoReader.Seek(offset, whence) } -//Close current io for ObjectReader +// Close current io for ObjectReader func (sr *shimObjectReader) Close() error { return sr.repoReader.Close() } diff --git a/pkg/uploader/kopia/snapshot.go b/pkg/uploader/kopia/snapshot.go index 904c235a1d..2137545cc7 100644 --- a/pkg/uploader/kopia/snapshot.go +++ b/pkg/uploader/kopia/snapshot.go @@ -5,7 +5,7 @@ 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 +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, @@ -18,6 +18,7 @@ package kopia import ( "context" + "fmt" "io/ioutil" "math" "os" @@ -43,14 +44,14 @@ import ( "github.com/pkg/errors" ) -//All function mainly used to make testing more convenient +// All function mainly used to make testing more convenient var treeForSourceFunc = policy.TreeForSource var applyRetentionPolicyFunc = policy.ApplyRetentionPolicy var setPolicyFunc = policy.SetPolicy var saveSnapshotFunc = snapshot.SaveSnapshot var loadSnapshotFunc = snapshot.LoadSnapshot -//SnapshotUploader which mainly used for UT test that could overwrite Upload interface +// SnapshotUploader which mainly used for UT test that could overwrite Upload interface type SnapshotUploader interface { Upload( ctx context.Context, @@ -61,11 +62,17 @@ type SnapshotUploader interface { ) (*snapshot.Manifest, error) } -func newOptionalInt(b policy.OptionalInt) *policy.OptionalInt { - return &b +func newOptionalInt(b int) *policy.OptionalInt { + ob := policy.OptionalInt(b) + return &ob } -//setupDefaultPolicy set default policy for kopia +func newOptionalBool(b bool) *policy.OptionalBool { + ob := policy.OptionalBool(b) + return &ob +} + +// setupDefaultPolicy set default policy for kopia func setupDefaultPolicy(ctx context.Context, rep repo.RepositoryWriter, sourceInfo snapshot.SourceInfo) error { return setPolicyFunc(ctx, rep, sourceInfo, &policy.Policy{ RetentionPolicy: policy.RetentionPolicy{ @@ -75,15 +82,18 @@ func setupDefaultPolicy(ctx context.Context, rep repo.RepositoryWriter, sourceIn CompressorName: "none", }, UploadPolicy: policy.UploadPolicy{ - MaxParallelFileReads: newOptionalInt(policy.OptionalInt(runtime.NumCPU())), + MaxParallelFileReads: newOptionalInt(runtime.NumCPU()), }, SchedulingPolicy: policy.SchedulingPolicy{ Manual: true, }, + ErrorHandlingPolicy: policy.ErrorHandlingPolicy{ + IgnoreUnknownTypes: newOptionalBool(true), + }, }) } -//Backup backup specific sourcePath and update progress +// Backup backup specific sourcePath and update progress func Backup(ctx context.Context, fsUploader *snapshotfs.Uploader, repoWriter repo.RepositoryWriter, sourcePath string, parentSnapshot string, log logrus.FieldLogger) (*uploader.SnapshotInfo, bool, error) { if fsUploader == nil { @@ -140,7 +150,7 @@ func getLocalFSEntry(path0 string) (fs.Entry, error) { return e, nil } -//resolveSymlink returns the path name after the evaluation of any symbolic links +// resolveSymlink returns the path name after the evaluation of any symbolic links func resolveSymlink(path string) (string, error) { st, err := os.Lstat(path) if err != nil { @@ -154,7 +164,7 @@ func resolveSymlink(path string) (string, error) { return filepath.EvalSymlinks(path) } -//SnapshotSource which setup policy for snapshot, upload snapshot, update progress +// SnapshotSource which setup policy for snapshot, upload snapshot, update progress func SnapshotSource( ctx context.Context, rep repo.RepositoryWriter, @@ -212,19 +222,23 @@ func SnapshotSource( return "", 0, errors.Wrapf(err, "Failed to flush kopia repository") } log.Infof("Created snapshot with root %v and ID %v in %v", manifest.RootObjectID(), manifest.ID, time.Since(snapshotStartTime).Truncate(time.Second)) - return reportSnapshotStatus(manifest) + return reportSnapshotStatus(manifest, policyTree) } -func reportSnapshotStatus(manifest *snapshot.Manifest) (string, int64, error) { +func reportSnapshotStatus(manifest *snapshot.Manifest, policyTree *policy.Tree) (string, int64, error) { manifestID := manifest.ID snapSize := manifest.Stats.TotalFileSize var errs []string if ds := manifest.RootEntry.DirSummary; ds != nil { for _, ent := range ds.FailedEntries { - errs = append(errs, ent.Error) + policy := policyTree.DefinedPolicy() + if !(policy != nil && *policy.ErrorHandlingPolicy.IgnoreUnknownTypes == true && strings.Contains(ent.Error, fs.ErrUnknown.Error())) { + errs = append(errs, fmt.Sprintf("Error when processing %v: %v", ent.EntryPath, ent.Error)) + } } } + if len(errs) != 0 { return "", 0, errors.New(strings.Join(errs, "\n")) } @@ -260,7 +274,7 @@ func findPreviousSnapshotManifest(ctx context.Context, rep repo.Repository, sour return result, nil } -//Restore restore specific sourcePath with given snapshotID and update progress +// Restore restore specific sourcePath with given snapshotID and update progress func Restore(ctx context.Context, rep repo.RepositoryWriter, progress *KopiaProgress, snapshotID, dest string, log logrus.FieldLogger, cancleCh chan struct{}) (int64, int32, error) { log.Info("Start to restore...") diff --git a/pkg/uploader/provider/kopia.go b/pkg/uploader/provider/kopia.go index 9890a8a91b..78aaa2655e 100644 --- a/pkg/uploader/provider/kopia.go +++ b/pkg/uploader/provider/kopia.go @@ -5,7 +5,7 @@ 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 +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, @@ -35,18 +35,18 @@ import ( "github.com/vmware-tanzu/velero/pkg/repository/udmrepo/service" ) -//BackupFunc mainly used to make testing more convenient +// BackupFunc mainly used to make testing more convenient var BackupFunc = kopia.Backup var RestoreFunc = kopia.Restore -//kopiaProvider recorded info related with kopiaProvider +// kopiaProvider recorded info related with kopiaProvider type kopiaProvider struct { bkRepo udmrepo.BackupRepo credGetter *credentials.CredentialGetter log logrus.FieldLogger } -//NewKopiaUploaderProvider initialized with open or create a repository +// NewKopiaUploaderProvider initialized with open or create a repository func NewKopiaUploaderProvider( ctx context.Context, credGetter *credentials.CredentialGetter, @@ -78,7 +78,7 @@ func NewKopiaUploaderProvider( return kp, nil } -//CheckContext check context status check if context is timeout or cancel and backup restore once finished it will quit and return +// CheckContext check context status check if context is timeout or cancel and backup restore once finished it will quit and return func (kp *kopiaProvider) CheckContext(ctx context.Context, finishChan chan struct{}, restoreChan chan struct{}, uploader *snapshotfs.Uploader) { select { case <-finishChan: @@ -119,10 +119,11 @@ func (kp *kopiaProvider) RunBackup( }) repoWriter := kopia.NewShimRepo(kp.bkRepo) kpUploader := snapshotfs.NewUploader(repoWriter) - prorgess := new(kopia.KopiaProgress) - prorgess.InitThrottle(backupProgressCheckInterval) - prorgess.Updater = updater - kpUploader.Progress = prorgess + progress := new(kopia.KopiaProgress) + progress.InitThrottle(backupProgressCheckInterval) + progress.Updater = updater + progress.Log = log + kpUploader.Progress = progress quit := make(chan struct{}) log.Info("Starting backup") go kp.CheckContext(ctx, quit, nil, kpUploader) @@ -165,7 +166,7 @@ func (kp *kopiaProvider) GetPassword(param interface{}) (string, error) { return strings.TrimSpace(rawPass), nil } -//RunRestore which will restore specific path and update restore progress +// RunRestore which will restore specific path and update restore progress func (kp *kopiaProvider) RunRestore( ctx context.Context, snapshotID string, diff --git a/pkg/uploader/provider/kopia_test.go b/pkg/uploader/provider/kopia_test.go index 955bf83f42..2284b73d7b 100644 --- a/pkg/uploader/provider/kopia_test.go +++ b/pkg/uploader/provider/kopia_test.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/uploader/provider/provider.go b/pkg/uploader/provider/provider.go index 3d0c452dc9..042b9f4407 100644 --- a/pkg/uploader/provider/provider.go +++ b/pkg/uploader/provider/provider.go @@ -71,7 +71,7 @@ func NewUploaderProvider( log logrus.FieldLogger, ) (Provider, error) { if credGetter.FromFile == nil { - return nil, errors.New("uninitialized FileStore credentail is not supported") + return nil, errors.New("uninitialized FileStore credential is not supported") } if uploaderType == uploader.KopiaType { // We use the hardcode repositoryType velerov1api.BackupRepositoryTypeKopia for now, because we have only one implementation of unified repo. diff --git a/pkg/uploader/provider/restic.go b/pkg/uploader/provider/restic.go index 00fe420571..70ae4823c0 100644 --- a/pkg/uploader/provider/restic.go +++ b/pkg/uploader/provider/restic.go @@ -137,7 +137,7 @@ func (rp *resticProvider) RunBackup( summary, stderrBuf, err := restic.RunBackup(backupCmd, log, updater) if err != nil { - if strings.Contains(err.Error(), "snapshot is empty") { + if strings.Contains(stderrBuf, "snapshot is empty") { log.Debugf("Restic backup got empty dir with %s path", path) return "", true, nil } diff --git a/pkg/uploader/types.go b/pkg/uploader/types.go index 015a2c156e..3f7979a9dd 100644 --- a/pkg/uploader/types.go +++ b/pkg/uploader/types.go @@ -5,7 +5,7 @@ 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 +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, @@ -41,13 +41,13 @@ type SnapshotInfo struct { Size int64 `json:"Size"` } -//UploaderProgress which defined two variables to record progress +// UploaderProgress which defined two variables to record progress type UploaderProgress struct { TotalBytes int64 `json:"totalBytes,omitempty"` BytesDone int64 `json:"doneBytes,omitempty"` } -//UploaderProgress which defined generic interface to update progress +// UploaderProgress which defined generic interface to update progress type ProgressUpdater interface { UpdateProgress(p *UploaderProgress) } diff --git a/pkg/util/kube/pod.go b/pkg/util/kube/pod.go index 4e589c4f04..81cd877979 100644 --- a/pkg/util/kube/pod.go +++ b/pkg/util/kube/pod.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/util/logging/format_flag.go b/pkg/util/logging/format_flag.go index fda083cb8d..c757a17dd7 100644 --- a/pkg/util/logging/format_flag.go +++ b/pkg/util/logging/format_flag.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/util/logging/log_counter_hook.go b/pkg/util/logging/log_counter_hook.go index d8d53359c1..7ee37e6601 100644 --- a/pkg/util/logging/log_counter_hook.go +++ b/pkg/util/logging/log_counter_hook.go @@ -17,45 +17,88 @@ limitations under the License. package logging import ( + "errors" + "fmt" "sync" "github.com/sirupsen/logrus" + + "github.com/vmware-tanzu/velero/pkg/util/results" ) -// LogCounterHook is a logrus hook that counts the number of log -// statements that have been written at each logrus level. -type LogCounterHook struct { - mu sync.RWMutex - counts map[logrus.Level]int +// LogHook is a logrus hook that counts the number of log +// statements that have been written at each logrus level. It also +// maintains log entries at each logrus level in result structure. +type LogHook struct { + mu sync.RWMutex + counts map[logrus.Level]int + entries map[logrus.Level]*results.Result } -// NewLogCounterHook returns a pointer to an initialized LogCounterHook. -func NewLogCounterHook() *LogCounterHook { - return &LogCounterHook{ - counts: make(map[logrus.Level]int), +// NewLogHook returns a pointer to an initialized LogHook. +func NewLogHook() *LogHook { + return &LogHook{ + counts: make(map[logrus.Level]int), + entries: make(map[logrus.Level]*results.Result), } } // Levels returns the logrus levels that the hook should be fired for. -func (h *LogCounterHook) Levels() []logrus.Level { +func (h *LogHook) Levels() []logrus.Level { return logrus.AllLevels } // Fire executes the hook's logic. -func (h *LogCounterHook) Fire(entry *logrus.Entry) error { +func (h *LogHook) Fire(entry *logrus.Entry) error { h.mu.Lock() defer h.mu.Unlock() h.counts[entry.Level]++ + if h.entries[entry.Level] == nil { + h.entries[entry.Level] = &results.Result{} + } + + namespace, isNamespacePresent := entry.Data["namespace"] + errorField, isErrorFieldPresent := entry.Data["error"] + resourceField, isResourceFieldPresent := entry.Data["resource"] + nameField, isNameFieldPresent := entry.Data["name"] + + entryMessage := "" + if isResourceFieldPresent { + entryMessage = fmt.Sprintf("%s resource: /%s", entryMessage, resourceField.(string)) + } + if isNameFieldPresent { + entryMessage = fmt.Sprintf("%s name: /%s", entryMessage, nameField.(string)) + } + if isErrorFieldPresent { + entryMessage = fmt.Sprintf("%s error: /%v", entryMessage, errorField) + } + if isNamespacePresent { + h.entries[entry.Level].Add(namespace.(string), errors.New(entryMessage)) + } else { + h.entries[entry.Level].AddVeleroError(errors.New(entryMessage)) + } return nil } // GetCount returns the number of log statements that have been // written at the specific level provided. -func (h *LogCounterHook) GetCount(level logrus.Level) int { +func (h *LogHook) GetCount(level logrus.Level) int { h.mu.RLock() defer h.mu.RUnlock() return h.counts[level] } + +// GetEntries returns the log statements that have been +// written at the specific level provided. +func (h *LogHook) GetEntries(level logrus.Level) results.Result { + h.mu.RLock() + defer h.mu.RUnlock() + response, isPresent := h.entries[level] + if isPresent { + return *response + } + return results.Result{} +} diff --git a/pkg/util/logging/log_level_flag.go b/pkg/util/logging/log_level_flag.go index 4d3d306d03..ff0f0efb22 100644 --- a/pkg/util/logging/log_level_flag.go +++ b/pkg/util/logging/log_level_flag.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/pkg/restore/result.go b/pkg/util/results/result.go similarity index 89% rename from pkg/restore/result.go rename to pkg/util/results/result.go index e8e1e7b30c..f211b8b178 100644 --- a/pkg/restore/result.go +++ b/pkg/util/results/result.go @@ -14,10 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -package restore +package results // Result is a collection of messages that were generated during -// execution of a restore. This will typically store either +// execution of a backup or restore. This will typically store either // warning or error messages. type Result struct { // Velero is a slice of messages related to the operation of Velero @@ -25,12 +25,12 @@ type Result struct { // cloud, reading a backup file, etc.) Velero []string `json:"velero,omitempty"` - // Cluster is a slice of messages related to restoring cluster- - // scoped resources. + // Cluster is a slice of messages related to backup or restore of + // cluster-scoped resources. Cluster []string `json:"cluster,omitempty"` // Namespaces is a map of namespace name to slice of messages - // related to restoring namespace-scoped resources. + // related to backup or restore namespace-scoped resources. Namespaces map[string][]string `json:"namespaces,omitempty"` } diff --git a/pkg/restore/result_test.go b/pkg/util/results/result_test.go similarity index 99% rename from pkg/restore/result_test.go rename to pkg/util/results/result_test.go index c710372245..f447c9d1e7 100644 --- a/pkg/restore/result_test.go +++ b/pkg/util/results/result_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package restore +package results import ( "testing" diff --git a/site/config.yaml b/site/config.yaml index ee1a7cb4b4..92b67cf38b 100644 --- a/site/config.yaml +++ b/site/config.yaml @@ -12,10 +12,10 @@ params: hero: backgroundColor: med-blue versioning: true - latest: v1.10.0-rc.1 + latest: v1.10 versions: - main - - v1.10.0-rc.1 + - v1.10 - v1.9 - v1.8 - v1.7 diff --git a/site/content/docs/main/api-types/backup.md b/site/content/docs/main/api-types/backup.md index 1c1d0b9aad..a93da8e271 100644 --- a/site/content/docs/main/api-types/backup.md +++ b/site/content/docs/main/api-types/backup.md @@ -48,7 +48,13 @@ spec: # or fully-qualified. Optional. excludedResources: - storageclasses.storage.k8s.io - # Whether or not to include cluster-scoped resources. Valid values are true, false, and + # Order of the resources to be collected during the backup process. It's a map with key being the plural resource + # name, and the value being a list of object names separated by comma. Each resource name has format "namespace/objectname". + # For cluster resources, simply use "objectname". Optional + orderedResources: + pods: mysql/mysql-cluster-replica-0,mysql/mysql-cluster-replica-1,mysql/mysql-cluster-source-0 + persistentvolumes: pvc-87ae0832-18fd-4f40-a2a4-5ed4242680c4,pvc-63be1bb0-90f5-4629-a7db-b8ce61ee29b3 + # Whether to include cluster-scoped resources. Valid values are true, false, and # null/unset. If true, all cluster-scoped resources are included (subject to included/excluded # resources and the label selector). If false, no cluster-scoped resources are included. If unset, # all cluster-scoped resources are included if and only if all namespaces are included and there are @@ -157,5 +163,4 @@ status: errors: 0 # An error that caused the entire backup to fail. failureReason: "" - ``` diff --git a/site/content/docs/main/api-types/restore.md b/site/content/docs/main/api-types/restore.md index ae4354666b..a6495c8254 100644 --- a/site/content/docs/main/api-types/restore.md +++ b/site/content/docs/main/api-types/restore.md @@ -29,8 +29,12 @@ metadata: namespace: velero # Parameters about the restore. Required. spec: - # BackupName is the unique name of the Velero backup to restore from. + # The unique name of the Velero backup to restore from. backupName: a-very-special-backup + # The unique name of the Velero schedule + # to restore from. If specified, and BackupName is empty, Velero will + # restore from the most recent successful backup created from this schedule. + scheduleName: my-scheduled-backup-name # Array of namespaces to include in the restore. If unspecified, all namespaces are included. # Optional. includedNamespaces: @@ -83,19 +87,18 @@ spec: app: velero - matchLabels: app: data-protection - # NamespaceMapping is a map of source namespace names to + # namespaceMapping is a map of source namespace names to # target namespace names to restore into. Any source namespaces not # included in the map will be restored into namespaces of the same name. namespaceMapping: namespace-backup-from: namespace-to-restore-to - # RestorePVs specifies whether to restore all included PVs - # from snapshot (via the cloudprovider). + # restorePVs specifies whether to restore all included PVs + # from snapshot (via the cloudprovider). Optional restorePVs: true - # ScheduleName is the unique name of the Velero schedule - # to restore from. If specified, and BackupName is empty, Velero will - # restore from the most recent successful backup created from this schedule. - scheduleName: my-scheduled-backup-name - # ExistingResourcePolicy specifies the restore behaviour + # preserveNodePorts specifies whether to restore old nodePorts from backup, + # so that the exposed port numbers on the node will remain the same after restore. Optional + preserveNodePorts: true + # existingResourcePolicy specifies the restore behaviour # for the kubernetes resource to be restored. Optional existingResourcePolicy: none # Actions to perform during or post restore. The only hooks currently supported are diff --git a/site/content/docs/main/api-types/schedule.md b/site/content/docs/main/api-types/schedule.md index eb3aa271ba..4b759501af 100644 --- a/site/content/docs/main/api-types/schedule.md +++ b/site/content/docs/main/api-types/schedule.md @@ -32,6 +32,9 @@ metadata: spec: # Schedule is a Cron expression defining when to run the Backup schedule: 0 7 * * * + # Specifies whether to use OwnerReferences on backups created by this Schedule. + # Notice: if set to true, when schedule is deleted, backups will be deleted too. Optional. + useOwnerReferencesInBackup: false # Template is the spec that should be used for each backup triggered by this schedule. template: # CSISnapshotTimeout specifies the time used to wait for @@ -53,7 +56,10 @@ spec: # or fully-qualified. Optional. excludedResources: - storageclasses.storage.k8s.io - # Whether or not to include cluster-scoped resources. Valid values are true, false, and + orderedResources: + pods: mysql/mysql-cluster-replica-0,mysql/mysql-cluster-replica-1,mysql/mysql-cluster-source-0 + persistentvolumes: pvc-87ae0832-18fd-4f40-a2a4-5ed4242680c4,pvc-63be1bb0-90f5-4629-a7db-b8ce61ee29b3 + # Whether to include cluster-scoped resources. Valid values are true, false, and # null/unset. If true, all cluster-scoped resources are included (subject to included/excluded # resources and the label selector). If false, no cluster-scoped resources are included. If unset, # all cluster-scoped resources are included if and only if all namespaces are included and there are @@ -68,7 +74,7 @@ spec: matchLabels: app: velero component: server - # Whether or not to snapshot volumes. This only applies to PersistentVolumes for Azure, GCE, and + # Whether to snapshot volumes. This only applies to PersistentVolumes for Azure, GCE, and # AWS. Valid values are true, false, and null/unset. If unset, Velero performs snapshots as long as # a persistent volume provider is configured for Velero. snapshotVolumes: null @@ -136,9 +142,6 @@ spec: # processed. Only "exec" hooks are supported. post: # Same content as pre above. - # Specifies whether to use OwnerReferences on backups created by this Schedule. - # Notice: if set to true, when schedule is deleted, backups will be deleted too. Optional. - useOwnerReferencesInBackup: false status: # The current phase of the latest scheduled backup. Valid values are New, FailedValidation, InProgress, Completed, PartiallyFailed, Failed. phase: "" diff --git a/site/content/docs/main/file-system-backup.md b/site/content/docs/main/file-system-backup.md index 5b1043c63b..881747895c 100644 --- a/site/content/docs/main/file-system-backup.md +++ b/site/content/docs/main/file-system-backup.md @@ -347,9 +347,9 @@ to be defined by its pod. - Even though the backup data could be incrementally preserved, for a single file data, FSB leverages on deduplication to find the difference to be saved. This means that large files (such as ones storing a database) will take a long time to scan for data deduplication, even if the actual difference is small. -- You may need to [customize the resource limits](/docs/main/customize-installation/#customize-resource-requests-and-limits) +- You may need to [customize the resource limits](customize-installation/#customize-resource-requests-and-limits) to make sure backups complete successfully for massive small files or large backup size cases, for more details refer to -[Velero File System Backup Performance Guide](https://empty-to-be-created). +[Velero File System Backup Performance Guide](/docs/main/performance-guidance). - Velero's File System Backup reads/writes data from volumes by accessing the node's filesystem, on which the pod is running. For this reason, FSB can only backup volumes that are mounted by a pod and not directly from the PVC. For orphan PVC/PV pairs (without running pods), some Velero users overcame this limitation running a staging pod (i.e. a busybox or alpine container diff --git a/site/content/docs/main/performance-guidance.md b/site/content/docs/main/performance-guidance.md new file mode 100644 index 0000000000..dc8284ecf6 --- /dev/null +++ b/site/content/docs/main/performance-guidance.md @@ -0,0 +1,163 @@ +--- +title: "Velero File System Backup Performance Guide" +layout: docs +--- + +When using Velero to do file system backup & restore, Restic uploader or Kopia uploader are both supported now. But the resources used and time consumption are a big difference between them. + +We've done series rounds of tests against Restic uploader and Kopia uploader through Velero, which may give you some guidance. But the test results will vary from different infrastructures, and our tests are limited and couldn't cover a variety of data scenarios, **the test results and analysis are for reference only**. + +## Infrastructure + +Minio is used as Velero backend storage, Network File System (NFS) is used to create the persistent volumes (PVs) and Persistent Volume Claims (PVC) based on the storage. The minio and NFS server are deployed independently in different virtual machines (VM), which with 300 MB/s write throughput and 175 MB/s read throughput representatively. + +The details of environmental information as below: + +``` +### KUBERNETES VERSION +root@velero-host-01:~# kubectl version +Client Version: version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.4" +Server Version: version.Info{Major:"1", Minor:"21", GitVersion:"v1.21.14" + +### DOCKER VERSION +root@velero-host-01:~# docker version +Client: + Version: 20.10.12 + API version: 1.41 + +Server: + Engine: + Version: 20.10.12 + API version: 1.41 (minimum version 1.12) + Go version: go1.16.2 + containerd: + Version: 1.5.9-0ubuntu1~20.04.4 + runc: + Version: 1.1.0-0ubuntu1~20.04.1 + docker-init: + Version: 0.19.0 + +### NODES +root@velero-host-01:~# kubectl get nodes |wc -l +6 // one master with 6 work nodes + +### DISK INFO +root@velero-host-01:~# smartctl -a /dev/sda +smartctl 7.1 2019-12-30 r5022 [x86_64-linux-5.4.0-126-generic] (local build) +Copyright (C) 2002-19, Bruce Allen, Christian Franke, www.smartmontools.org + +=== START OF INFORMATION SECTION === +Vendor: VMware +Product: Virtual disk +Revision: 1.0 +Logical block size: 512 bytes +Rotation Rate: Solid State Device +Device type: disk +### MEMORY INFO +root@velero-host-01:~# free -h + total used free shared buff/cache available +Mem: 3.8Gi 328Mi 3.1Gi 1.0Mi 469Mi 3.3Gi +Swap: 0B 0B 0B + +### CPU INFO +root@velero-host-01:~# cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c + 4 Intel(R) Xeon(R) Gold 6230R CPU @ 2.10GHz + +### SYSTEM INFO +root@velero-host-01:~# cat /proc/version +root@velero-host-01:~# cat /proc/version +Linux version 5.4.0-126-generic (build@lcy02-amd64-072) (gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1)) #142-Ubuntu SMP Fri Aug 26 12:12:57 UTC 2022 + +### VELERO VERSION +root@velero-host-01:~# velero version +Client: + Version: main ###v1.10 pre-release version + Git commit: 9b22ca6100646523876b18a491d881561b4dbcf3-dirty +Server: + Version: main ###v1.10 pre-release version +``` + +## Test + +Below we've done 6 groups of tests, for each single group of test, we used limited resources (1 core CPU 2 GB memory or 4 cores CPU 4 GB memory) to do Velero file system backup under Restic path and Kopia path, and then compare the results. + +Recorded the metrics of time consumption, maximum CPU usage, maximum memory usage, and minio storage usage for node-agent daemonset, and the metrics of Velero deployment are not included since the differences are not obvious by whether using Restic uploader or Kopia uploader. + +Compression is either disabled or not unavailable for both uploader. + +### Case 1: 4194304(4M) files, 2396745(2M) directories, 0B per file total 0B content +#### result: +|Uploader| Resources|Times |Max CPU|Max Memory|Repo Usage| +|--------|----------|:----:|------:|:--------:|:--------:| +| Kopia | 1c2g |24m54s| 65% |1530 MB |80 MB | +| Restic | 1c2g |52m31s| 55% |1708 MB |3.3 GB | +| Kopia | 4c4g |24m52s| 63% |2216 MB |80 MB | +| Restic | 4c4g |52m28s| 54% |2329 MB |3.3 GB | +#### conclusion: +- The memory usage is larger than Velero's default memory limit (1GB) for both Kopia and Restic under massive empty files. +- For both using Kopia uploader and Restic uploader, there is no significant time reduction by increasing resources from 1c2g to 4c4g. +- Restic uploader is one more time slower than Kopia uploader under the same specification resources. +- Restic has an **irrational** repository size (3.3GB) + +### Case 2: Using the same size (100B) of file and default Velero's resource configuration, the testing quantity of files from 20 thousand to 2 million, these groups of cases mainly test the behavior with the increasing quantity of files. + +### Case 2.1: 235298(23K) files, 137257 (10k)directories, 100B per file total 22.440MB content +#### result: +| Uploader | Resources|Times |Max CPU|Max Memory|Repo Usage| +|-------|----------|:----:|------:|:--------:|:--------:| +| Kopia | 1c1g |2m34s | 70% |692 MB |108 MB | +| Restic| 1c1g |3m9s | 54% |714 MB |275 MB | + +### Case 2.2 470596(40k) files, 137257 (10k)directories, 100B per file total 44.880MB content +#### result: +| Uploader | Resources|Times |Max CPU|Max Memory|Repo Usage| +|-------|----------|:----:|------:|:--------:|:--------:| +| Kopia | 1c1g |3m45s | 68% |831 MB |108 MB | +| Restic| 1c1g |4m53s | 57% |788 MB |275 MB | + +### Case 2.3 705894(70k) files, 137257(10k) directories, 100B per file total 67.319MB content +#### result: +|Uploader| Resources|Times |Max CPU|Max Memory|Repo Usage| +|--------|----------|:----:|------:|:--------:|:--------:| +| Kopia | 1c1g |5m06s | 71% |861 MB |108 MB | +| Restic | 1c1g |6m23s | 56% |810 MB |275 MB | + +### Case 2.4 2097152(2M) files, 2396745(2M) directories, 100B per file total 200.000MB content +#### result: +|Uploader| Resources|Times |Max CPU|Max Memory|Repo Usage| +|--------|----------|:----:|------:|:--------:|:--------:| +| Kopia | 1c1g |OOM | 74% |N/A |N/A | +| Restic | 1c1g |41m47s| 52% |904 MB |3.2 GB | +#### conclusion: +- With the increasing number of files, there is no memory abnormal surge, the memory usage for both Kopia uploader and Restic uploader is linear increasing, until exceeds 1GB memory usage in Case 2.4 Kopia uploader OOM happened. +- Kopia uploader gets increasingly faster along with the increasing number of files. +- Restic uploader repository size is still much larger than Kopia uploader repository. + +### Case 3: 10625(10k) files, 781 directories, 1.000MB per file total 10.376GB content +#### result: +|Uploader| Resources|Times |Max CPU|Max Memory|Repo Usage| +|--------|----------|:----:|------:|:--------:|:--------:| +| Kopia | 1c2g |1m37s | 75% |251 MB |10 GB | +| Restic | 1c2g |5m25s | 100% |153 MB |10 GB | +| Kopia | 4c4g |1m35s | 75% |248 MB |10 GB | +| Restic | 4c4g |3m17s | 171% |126 MB |10 GB | +#### conclusion: +- This case involves a relatively large backup size, there is no significant time reduction by increasing resources from 1c2g to 4c4g for Kopia uploader, but for Restic upoader when increasing CPU from 1 core to 4, backup time-consuming was shortened by one-third, which means in this scenario should allocate more CPU resources for Restic uploader. +- For the large backup size case, Restic uploader's repository size comes to normal + +### Case 4: 900 files, 1 directory, 1.000GB per file total 900.000GB content +#### result: +|Uploader| Resources|Times |Max CPU|Max Memory|Repo Usage| +|--------|----------|:-----:|------:|:--------:|:--------:| +| Kopia | 1c2g |2h30m | 100% |714 MB |900 GB | +| Restic | 1c2g |Timeout| 100% |416 MB |N/A | +| Kopia | 4c4g |1h42m | 138% |786 MB |900 GB | +| Restic | 4c4g |2h15m | 351% |606 MB |900 GB | +#### conclusion: +- When the target backup data is relatively large, Restic uploader starts to Timeout under 1c2g. So it's better to allocate more memory for Restic uploader when backup large sizes of data. +- For backup large amounts of data, Kopia uploader is both less time-consuming and less resource usage. + +## Summary +- With the same specification resources, Kopia uploader is less time-consuming when backup. +- Performance would be better if choosing Kopia uploader for the scenario in backup large mounts of data or massive small files. +- It's better to set one reasonable resource configuration instead of the default depending on your scenario. For default resource configuration, it's easy to be timeout with Restic uploader in backup large amounts of data, and it's easy to be OOM for both Kopia uploader and Restic uploader in backup of massive small files. \ No newline at end of file diff --git a/site/content/docs/main/proxy.md b/site/content/docs/main/proxy.md new file mode 100644 index 0000000000..047a4ac4cb --- /dev/null +++ b/site/content/docs/main/proxy.md @@ -0,0 +1,64 @@ +--- +title: "Behind Proxy" +layout: docs +toc: "true" +--- + +This document explains how to make Velero work behind proxy. +The procedures described in this document are concluded from the scenario that Velero is deployed behind proxy, and Velero needs to connect to a public MinIO server as storage location. Maybe other scenarios' configurations are not exactly the same, but basically they should share most parts. + +## Set the proxy server address +Specify the proxy server address by environment variables in Velero deployment and node-agent DaemonSet. +Take the following as an example: +``` yaml + ... + spec: + containers: + - args: + - server + - --features=EnableCSI + command: + - /velero + env: + ... + - name: HTTP_PROXY + value: + - name: HTTPS_PROXY + value: + # In case not all destinations that Velero connects to need go through proxy, users can specify the NO_PROXY to bypass proxy. + - name: NO_PROXY + value: +``` + +## Set the proxy required certificates +In some cases, the proxy requires certificate to connect. Set the certificate in the BSL's `Spec.ObjectStorage.CACert`. +It's possible that the object storage also requires certificate, and it's also set in `Spec.ObjectStorage.CACert`, then set both certificates in `Spec.ObjectStorage.CACert` field. + +The following is an example file contains two certificates, then encode its content with base64, and set the encode result in the BSL. + +``` bash +cat certs +-----BEGIN CERTIFICATE----- +certificates first content +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +certificates second content +-----END CERTIFICATE----- + +cat certs | base64 +LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCmNlcnRpZmljYXRlcyBmaXJzdCBjb250ZW50Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpjZXJ0aWZpY2F0ZXMgc2Vjb25kIGNvbnRlbnQKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= +``` + +``` yaml + apiVersion: velero.io/v1 + kind: BackupStorageLocation + ... + spec: + ... + default: true + objectStorage: + bucket: velero + caCert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCmNlcnRpZmljYXRlcyBmaXJzdCBjb250ZW50Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpjZXJ0aWZpY2F0ZXMgc2Vjb25kIGNvbnRlbnQKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + ... +``` diff --git a/site/content/docs/main/upgrade-to-1.10.md b/site/content/docs/main/upgrade-to-1.10.md index e0b23bb156..1eb8056ab0 100644 --- a/site/content/docs/main/upgrade-to-1.10.md +++ b/site/content/docs/main/upgrade-to-1.10.md @@ -58,12 +58,12 @@ Before upgrading, check the [Velero compatibility matrix](https://github.com/vmw | kubectl apply -f - # optional, if using the restic daemon set - dsjson=$(kubectl get ds -n velero -ojson) - kubectl delete ds -n velero --all --force --grace-period 0 - echo $dsjson | sed "s#\"image\"\: \"velero\/velero\:v[0-9]*.[0-9]*.[0-9]\"#\"image\"\: \"velero\/velero\:v1.10.0\"#g" \ + echo $(kubectl get ds -n velero restic -ojson) \ + | sed "s#\"image\"\: \"velero\/velero\:v[0-9]*.[0-9]*.[0-9]\"#\"image\"\: \"velero\/velero\:v1.10.0\"#g" \ | sed "s#\"name\"\: \"restic\"#\"name\"\: \"node-agent\"#g" \ | sed "s#\[ \"restic\",#\[ \"node-agent\",#g" \ | kubectl apply -f - + kubectl delete ds -n velero restic --force --grace-period 0 ``` 1. Confirm that the deployment is up and running with the correct version by running: diff --git a/site/content/docs/v1.10.0-rc.1/_index.md b/site/content/docs/v1.10/_index.md similarity index 93% rename from site/content/docs/v1.10.0-rc.1/_index.md rename to site/content/docs/v1.10/_index.md index e2d39eae7c..492121411a 100644 --- a/site/content/docs/v1.10.0-rc.1/_index.md +++ b/site/content/docs/v1.10/_index.md @@ -1,7 +1,7 @@ --- toc: "false" cascade: - version: v1.10.0-rc.1 + version: v1.10 toc: "true" --- ![100] @@ -33,7 +33,7 @@ If you encounter issues, review the [troubleshooting docs][30], [file an issue][ ## Contributing -If you are ready to jump in and test, add code, or help with documentation, follow the instructions on our [Start contributing](https://velero.io/docs/v1.10.0-rc.1/start-contributing/) documentation for guidance on how to setup Velero for development. +If you are ready to jump in and test, add code, or help with documentation, follow the instructions on our [Start contributing](https://velero.io/docs/v1.10/start-contributing/) documentation for guidance on how to setup Velero for development. ## Changelog diff --git a/site/content/docs/v1.10.0-rc.1/api-types/README.md b/site/content/docs/v1.10/api-types/README.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/api-types/README.md rename to site/content/docs/v1.10/api-types/README.md diff --git a/site/content/docs/v1.10.0-rc.1/api-types/_index.md b/site/content/docs/v1.10/api-types/_index.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/api-types/_index.md rename to site/content/docs/v1.10/api-types/_index.md diff --git a/site/content/docs/v1.10.0-rc.1/api-types/backup.md b/site/content/docs/v1.10/api-types/backup.md similarity index 91% rename from site/content/docs/v1.10.0-rc.1/api-types/backup.md rename to site/content/docs/v1.10/api-types/backup.md index 1c1d0b9aad..a93da8e271 100644 --- a/site/content/docs/v1.10.0-rc.1/api-types/backup.md +++ b/site/content/docs/v1.10/api-types/backup.md @@ -48,7 +48,13 @@ spec: # or fully-qualified. Optional. excludedResources: - storageclasses.storage.k8s.io - # Whether or not to include cluster-scoped resources. Valid values are true, false, and + # Order of the resources to be collected during the backup process. It's a map with key being the plural resource + # name, and the value being a list of object names separated by comma. Each resource name has format "namespace/objectname". + # For cluster resources, simply use "objectname". Optional + orderedResources: + pods: mysql/mysql-cluster-replica-0,mysql/mysql-cluster-replica-1,mysql/mysql-cluster-source-0 + persistentvolumes: pvc-87ae0832-18fd-4f40-a2a4-5ed4242680c4,pvc-63be1bb0-90f5-4629-a7db-b8ce61ee29b3 + # Whether to include cluster-scoped resources. Valid values are true, false, and # null/unset. If true, all cluster-scoped resources are included (subject to included/excluded # resources and the label selector). If false, no cluster-scoped resources are included. If unset, # all cluster-scoped resources are included if and only if all namespaces are included and there are @@ -157,5 +163,4 @@ status: errors: 0 # An error that caused the entire backup to fail. failureReason: "" - ``` diff --git a/site/content/docs/v1.10.0-rc.1/api-types/backupstoragelocation.md b/site/content/docs/v1.10/api-types/backupstoragelocation.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/api-types/backupstoragelocation.md rename to site/content/docs/v1.10/api-types/backupstoragelocation.md diff --git a/site/content/docs/v1.10.0-rc.1/api-types/restore.md b/site/content/docs/v1.10/api-types/restore.md similarity index 94% rename from site/content/docs/v1.10.0-rc.1/api-types/restore.md rename to site/content/docs/v1.10/api-types/restore.md index ae4354666b..a6495c8254 100644 --- a/site/content/docs/v1.10.0-rc.1/api-types/restore.md +++ b/site/content/docs/v1.10/api-types/restore.md @@ -29,8 +29,12 @@ metadata: namespace: velero # Parameters about the restore. Required. spec: - # BackupName is the unique name of the Velero backup to restore from. + # The unique name of the Velero backup to restore from. backupName: a-very-special-backup + # The unique name of the Velero schedule + # to restore from. If specified, and BackupName is empty, Velero will + # restore from the most recent successful backup created from this schedule. + scheduleName: my-scheduled-backup-name # Array of namespaces to include in the restore. If unspecified, all namespaces are included. # Optional. includedNamespaces: @@ -83,19 +87,18 @@ spec: app: velero - matchLabels: app: data-protection - # NamespaceMapping is a map of source namespace names to + # namespaceMapping is a map of source namespace names to # target namespace names to restore into. Any source namespaces not # included in the map will be restored into namespaces of the same name. namespaceMapping: namespace-backup-from: namespace-to-restore-to - # RestorePVs specifies whether to restore all included PVs - # from snapshot (via the cloudprovider). + # restorePVs specifies whether to restore all included PVs + # from snapshot (via the cloudprovider). Optional restorePVs: true - # ScheduleName is the unique name of the Velero schedule - # to restore from. If specified, and BackupName is empty, Velero will - # restore from the most recent successful backup created from this schedule. - scheduleName: my-scheduled-backup-name - # ExistingResourcePolicy specifies the restore behaviour + # preserveNodePorts specifies whether to restore old nodePorts from backup, + # so that the exposed port numbers on the node will remain the same after restore. Optional + preserveNodePorts: true + # existingResourcePolicy specifies the restore behaviour # for the kubernetes resource to be restored. Optional existingResourcePolicy: none # Actions to perform during or post restore. The only hooks currently supported are diff --git a/site/content/docs/v1.10.0-rc.1/api-types/schedule.md b/site/content/docs/v1.10/api-types/schedule.md similarity index 91% rename from site/content/docs/v1.10.0-rc.1/api-types/schedule.md rename to site/content/docs/v1.10/api-types/schedule.md index eb3aa271ba..4b759501af 100644 --- a/site/content/docs/v1.10.0-rc.1/api-types/schedule.md +++ b/site/content/docs/v1.10/api-types/schedule.md @@ -32,6 +32,9 @@ metadata: spec: # Schedule is a Cron expression defining when to run the Backup schedule: 0 7 * * * + # Specifies whether to use OwnerReferences on backups created by this Schedule. + # Notice: if set to true, when schedule is deleted, backups will be deleted too. Optional. + useOwnerReferencesInBackup: false # Template is the spec that should be used for each backup triggered by this schedule. template: # CSISnapshotTimeout specifies the time used to wait for @@ -53,7 +56,10 @@ spec: # or fully-qualified. Optional. excludedResources: - storageclasses.storage.k8s.io - # Whether or not to include cluster-scoped resources. Valid values are true, false, and + orderedResources: + pods: mysql/mysql-cluster-replica-0,mysql/mysql-cluster-replica-1,mysql/mysql-cluster-source-0 + persistentvolumes: pvc-87ae0832-18fd-4f40-a2a4-5ed4242680c4,pvc-63be1bb0-90f5-4629-a7db-b8ce61ee29b3 + # Whether to include cluster-scoped resources. Valid values are true, false, and # null/unset. If true, all cluster-scoped resources are included (subject to included/excluded # resources and the label selector). If false, no cluster-scoped resources are included. If unset, # all cluster-scoped resources are included if and only if all namespaces are included and there are @@ -68,7 +74,7 @@ spec: matchLabels: app: velero component: server - # Whether or not to snapshot volumes. This only applies to PersistentVolumes for Azure, GCE, and + # Whether to snapshot volumes. This only applies to PersistentVolumes for Azure, GCE, and # AWS. Valid values are true, false, and null/unset. If unset, Velero performs snapshots as long as # a persistent volume provider is configured for Velero. snapshotVolumes: null @@ -136,9 +142,6 @@ spec: # processed. Only "exec" hooks are supported. post: # Same content as pre above. - # Specifies whether to use OwnerReferences on backups created by this Schedule. - # Notice: if set to true, when schedule is deleted, backups will be deleted too. Optional. - useOwnerReferencesInBackup: false status: # The current phase of the latest scheduled backup. Valid values are New, FailedValidation, InProgress, Completed, PartiallyFailed, Failed. phase: "" diff --git a/site/content/docs/v1.10.0-rc.1/api-types/volumesnapshotlocation.md b/site/content/docs/v1.10/api-types/volumesnapshotlocation.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/api-types/volumesnapshotlocation.md rename to site/content/docs/v1.10/api-types/volumesnapshotlocation.md diff --git a/site/content/docs/v1.10.0-rc.1/backup-hooks.md b/site/content/docs/v1.10/backup-hooks.md similarity index 98% rename from site/content/docs/v1.10.0-rc.1/backup-hooks.md rename to site/content/docs/v1.10/backup-hooks.md index 9785f4a223..449cbeb8c7 100644 --- a/site/content/docs/v1.10.0-rc.1/backup-hooks.md +++ b/site/content/docs/v1.10/backup-hooks.md @@ -104,4 +104,4 @@ Note that the container must support the shell command you use. [1]: api-types/backup.md -[2]: https://github.com/vmware-tanzu/velero/blob/v1.10.0-rc.1/examples/nginx-app/with-pv.yaml +[2]: https://github.com/vmware-tanzu/velero/blob/v1.10.0/examples/nginx-app/with-pv.yaml diff --git a/site/content/docs/v1.10.0-rc.1/backup-reference.md b/site/content/docs/v1.10/backup-reference.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/backup-reference.md rename to site/content/docs/v1.10/backup-reference.md diff --git a/site/content/docs/v1.10.0-rc.1/basic-install.md b/site/content/docs/v1.10/basic-install.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/basic-install.md rename to site/content/docs/v1.10/basic-install.md diff --git a/site/content/docs/v1.10.0-rc.1/build-from-source.md b/site/content/docs/v1.10/build-from-source.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/build-from-source.md rename to site/content/docs/v1.10/build-from-source.md diff --git a/site/content/docs/v1.10.0-rc.1/code-standards.md b/site/content/docs/v1.10/code-standards.md similarity index 98% rename from site/content/docs/v1.10.0-rc.1/code-standards.md rename to site/content/docs/v1.10/code-standards.md index be403cfe0d..00b9e75125 100644 --- a/site/content/docs/v1.10.0-rc.1/code-standards.md +++ b/site/content/docs/v1.10/code-standards.md @@ -70,7 +70,7 @@ Example: We use a package to generate mocks for our interfaces. -Example: if you want to change this mock: https://github.com/vmware-tanzu/velero/blob/v1.10.0-rc.1/pkg/podvolume/mocks/restorer.go +Example: if you want to change this mock: https://github.com/vmware-tanzu/velero/blob/v1.10.0/pkg/podvolume/mocks/restorer.go Run: diff --git a/site/content/docs/v1.10.0-rc.1/contributions/ibm-config.md b/site/content/docs/v1.10/contributions/ibm-config.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/contributions/ibm-config.md rename to site/content/docs/v1.10/contributions/ibm-config.md diff --git a/site/content/docs/v1.10.0-rc.1/contributions/img-for-tencent/15ccaacf00640a04ae29ceed4c86195b.png b/site/content/docs/v1.10/contributions/img-for-tencent/15ccaacf00640a04ae29ceed4c86195b.png similarity index 100% rename from site/content/docs/v1.10.0-rc.1/contributions/img-for-tencent/15ccaacf00640a04ae29ceed4c86195b.png rename to site/content/docs/v1.10/contributions/img-for-tencent/15ccaacf00640a04ae29ceed4c86195b.png diff --git a/site/content/docs/v1.10.0-rc.1/contributions/img-for-tencent/1d53b0115644d43657c2a5ece805c9b4.png b/site/content/docs/v1.10/contributions/img-for-tencent/1d53b0115644d43657c2a5ece805c9b4.png similarity index 100% rename from site/content/docs/v1.10.0-rc.1/contributions/img-for-tencent/1d53b0115644d43657c2a5ece805c9b4.png rename to site/content/docs/v1.10/contributions/img-for-tencent/1d53b0115644d43657c2a5ece805c9b4.png diff --git a/site/content/docs/v1.10.0-rc.1/contributions/img-for-tencent/69194157ccd5e377d1e7d914fd8c0336.png b/site/content/docs/v1.10/contributions/img-for-tencent/69194157ccd5e377d1e7d914fd8c0336.png similarity index 100% rename from site/content/docs/v1.10.0-rc.1/contributions/img-for-tencent/69194157ccd5e377d1e7d914fd8c0336.png rename to site/content/docs/v1.10/contributions/img-for-tencent/69194157ccd5e377d1e7d914fd8c0336.png diff --git a/site/content/docs/v1.10.0-rc.1/contributions/img-for-tencent/9015313121ed7987558c88081b052574.png b/site/content/docs/v1.10/contributions/img-for-tencent/9015313121ed7987558c88081b052574.png similarity index 100% rename from site/content/docs/v1.10.0-rc.1/contributions/img-for-tencent/9015313121ed7987558c88081b052574.png rename to site/content/docs/v1.10/contributions/img-for-tencent/9015313121ed7987558c88081b052574.png diff --git a/site/content/docs/v1.10.0-rc.1/contributions/img-for-tencent/ceaca9ce6bc92bdce987c63d2fe71561.png b/site/content/docs/v1.10/contributions/img-for-tencent/ceaca9ce6bc92bdce987c63d2fe71561.png similarity index 100% rename from site/content/docs/v1.10.0-rc.1/contributions/img-for-tencent/ceaca9ce6bc92bdce987c63d2fe71561.png rename to site/content/docs/v1.10/contributions/img-for-tencent/ceaca9ce6bc92bdce987c63d2fe71561.png diff --git a/site/content/docs/v1.10.0-rc.1/contributions/img-for-tencent/e8c2ab4e5e31d1370c62fad25059a8a8.png b/site/content/docs/v1.10/contributions/img-for-tencent/e8c2ab4e5e31d1370c62fad25059a8a8.png similarity index 100% rename from site/content/docs/v1.10.0-rc.1/contributions/img-for-tencent/e8c2ab4e5e31d1370c62fad25059a8a8.png rename to site/content/docs/v1.10/contributions/img-for-tencent/e8c2ab4e5e31d1370c62fad25059a8a8.png diff --git a/site/content/docs/v1.10.0-rc.1/contributions/img-for-tencent/e932223585c0b19891cc085ad7f438e1.png b/site/content/docs/v1.10/contributions/img-for-tencent/e932223585c0b19891cc085ad7f438e1.png similarity index 100% rename from site/content/docs/v1.10.0-rc.1/contributions/img-for-tencent/e932223585c0b19891cc085ad7f438e1.png rename to site/content/docs/v1.10/contributions/img-for-tencent/e932223585c0b19891cc085ad7f438e1.png diff --git a/site/content/docs/v1.10.0-rc.1/contributions/img-for-tencent/eb2bbabae48b188748f5278bedf177f1.png b/site/content/docs/v1.10/contributions/img-for-tencent/eb2bbabae48b188748f5278bedf177f1.png similarity index 100% rename from site/content/docs/v1.10.0-rc.1/contributions/img-for-tencent/eb2bbabae48b188748f5278bedf177f1.png rename to site/content/docs/v1.10/contributions/img-for-tencent/eb2bbabae48b188748f5278bedf177f1.png diff --git a/site/content/docs/v1.10.0-rc.1/contributions/img-for-tencent/effe8a0a7ce3aa8e422db00bfdddc375.png b/site/content/docs/v1.10/contributions/img-for-tencent/effe8a0a7ce3aa8e422db00bfdddc375.png similarity index 100% rename from site/content/docs/v1.10.0-rc.1/contributions/img-for-tencent/effe8a0a7ce3aa8e422db00bfdddc375.png rename to site/content/docs/v1.10/contributions/img-for-tencent/effe8a0a7ce3aa8e422db00bfdddc375.png diff --git a/site/content/docs/v1.10.0-rc.1/contributions/img-for-tencent/f0fff5228527edc72d6e71a50d5dc966.png b/site/content/docs/v1.10/contributions/img-for-tencent/f0fff5228527edc72d6e71a50d5dc966.png similarity index 100% rename from site/content/docs/v1.10.0-rc.1/contributions/img-for-tencent/f0fff5228527edc72d6e71a50d5dc966.png rename to site/content/docs/v1.10/contributions/img-for-tencent/f0fff5228527edc72d6e71a50d5dc966.png diff --git a/site/content/docs/v1.10.0-rc.1/contributions/minio.md b/site/content/docs/v1.10/contributions/minio.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/contributions/minio.md rename to site/content/docs/v1.10/contributions/minio.md diff --git a/site/content/docs/v1.10.0-rc.1/contributions/oracle-config.md b/site/content/docs/v1.10/contributions/oracle-config.md similarity index 99% rename from site/content/docs/v1.10.0-rc.1/contributions/oracle-config.md rename to site/content/docs/v1.10/contributions/oracle-config.md index fc378e661d..9ac2400248 100644 --- a/site/content/docs/v1.10.0-rc.1/contributions/oracle-config.md +++ b/site/content/docs/v1.10/contributions/oracle-config.md @@ -244,5 +244,5 @@ After creating the Velero server in your cluster, try this example: ## Additional Reading -* [Official Velero Documentation](https://velero.io/docs/v1.10.0-rc.1/) +* [Official Velero Documentation](https://velero.io/docs/v1.10/) * [Oracle Cloud Infrastructure Documentation](https://docs.cloud.oracle.com/) diff --git a/site/content/docs/v1.10.0-rc.1/contributions/tencent-config.md b/site/content/docs/v1.10/contributions/tencent-config.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/contributions/tencent-config.md rename to site/content/docs/v1.10/contributions/tencent-config.md diff --git a/site/content/docs/v1.10.0-rc.1/csi.md b/site/content/docs/v1.10/csi.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/csi.md rename to site/content/docs/v1.10/csi.md diff --git a/site/content/docs/v1.10.0-rc.1/custom-plugins.md b/site/content/docs/v1.10/custom-plugins.md similarity index 98% rename from site/content/docs/v1.10.0-rc.1/custom-plugins.md rename to site/content/docs/v1.10/custom-plugins.md index aaf308f5cc..5fe168d75f 100644 --- a/site/content/docs/v1.10.0-rc.1/custom-plugins.md +++ b/site/content/docs/v1.10/custom-plugins.md @@ -112,4 +112,4 @@ Once parsed into a `[]string`, the features can then be registered using the `Ne Velero adds the `LD_LIBRARY_PATH` into the list of environment variables to provide the convenience for plugins that requires C libraries/extensions in the runtime. [1]: https://github.com/vmware-tanzu/velero-plugin-example -[2]: https://github.com/vmware-tanzu/velero/blob/v1.10.0-rc.1/pkg/plugin/logger.go +[2]: https://github.com/vmware-tanzu/velero/blob/v1.10.0/pkg/plugin/logger.go diff --git a/site/content/docs/v1.10.0-rc.1/customize-installation.md b/site/content/docs/v1.10/customize-installation.md similarity index 99% rename from site/content/docs/v1.10.0-rc.1/customize-installation.md rename to site/content/docs/v1.10/customize-installation.md index 862e1cd1b7..ab176ab947 100644 --- a/site/content/docs/v1.10.0-rc.1/customize-installation.md +++ b/site/content/docs/v1.10/customize-installation.md @@ -387,4 +387,4 @@ If you get an error like `complete:13: command not found: compdef`, then add the [8]: https://github.com/vmware-tanzu/velero/issues/2311 [9]: self-signed-certificates.md [10]: csi.md -[11]: https://github.com/vmware-tanzu/velero/blob/v1.10.0-rc.1/pkg/apis/velero/v1/constants.go +[11]: https://github.com/vmware-tanzu/velero/blob/v1.10.0/pkg/apis/velero/v1/constants.go diff --git a/site/content/docs/v1.10.0-rc.1/debugging-install.md b/site/content/docs/v1.10/debugging-install.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/debugging-install.md rename to site/content/docs/v1.10/debugging-install.md diff --git a/site/content/docs/v1.10.0-rc.1/debugging-restores.md b/site/content/docs/v1.10/debugging-restores.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/debugging-restores.md rename to site/content/docs/v1.10/debugging-restores.md diff --git a/site/content/docs/v1.10.0-rc.1/development.md b/site/content/docs/v1.10/development.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/development.md rename to site/content/docs/v1.10/development.md diff --git a/site/content/docs/v1.10.0-rc.1/disaster-case.md b/site/content/docs/v1.10/disaster-case.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/disaster-case.md rename to site/content/docs/v1.10/disaster-case.md diff --git a/site/content/docs/v1.10.0-rc.1/enable-api-group-versions-feature.md b/site/content/docs/v1.10/enable-api-group-versions-feature.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/enable-api-group-versions-feature.md rename to site/content/docs/v1.10/enable-api-group-versions-feature.md diff --git a/site/content/docs/v1.10.0-rc.1/examples.md b/site/content/docs/v1.10/examples.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/examples.md rename to site/content/docs/v1.10/examples.md diff --git a/site/content/docs/v1.10.0-rc.1/file-system-backup.md b/site/content/docs/v1.10/file-system-backup.md similarity index 99% rename from site/content/docs/v1.10.0-rc.1/file-system-backup.md rename to site/content/docs/v1.10/file-system-backup.md index 5b1043c63b..6fcd1a33b2 100644 --- a/site/content/docs/v1.10.0-rc.1/file-system-backup.md +++ b/site/content/docs/v1.10/file-system-backup.md @@ -347,9 +347,9 @@ to be defined by its pod. - Even though the backup data could be incrementally preserved, for a single file data, FSB leverages on deduplication to find the difference to be saved. This means that large files (such as ones storing a database) will take a long time to scan for data deduplication, even if the actual difference is small. -- You may need to [customize the resource limits](/docs/main/customize-installation/#customize-resource-requests-and-limits) +- You may need to [customize the resource limits](customize-installation/#customize-resource-requests-and-limits) to make sure backups complete successfully for massive small files or large backup size cases, for more details refer to -[Velero File System Backup Performance Guide](https://empty-to-be-created). +[Velero File System Backup Performance Guide](performance-guidance). - Velero's File System Backup reads/writes data from volumes by accessing the node's filesystem, on which the pod is running. For this reason, FSB can only backup volumes that are mounted by a pod and not directly from the PVC. For orphan PVC/PV pairs (without running pods), some Velero users overcame this limitation running a staging pod (i.e. a busybox or alpine container diff --git a/site/content/docs/v1.10.0-rc.1/how-velero-works.md b/site/content/docs/v1.10/how-velero-works.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/how-velero-works.md rename to site/content/docs/v1.10/how-velero-works.md diff --git a/site/content/docs/v1.10.0-rc.1/image-tagging.md b/site/content/docs/v1.10/image-tagging.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/image-tagging.md rename to site/content/docs/v1.10/image-tagging.md diff --git a/site/content/docs/v1.10.0-rc.1/img/README.md b/site/content/docs/v1.10/img/README.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/img/README.md rename to site/content/docs/v1.10/img/README.md diff --git a/site/content/docs/v1.10.0-rc.1/img/backup-process.png b/site/content/docs/v1.10/img/backup-process.png similarity index 100% rename from site/content/docs/v1.10.0-rc.1/img/backup-process.png rename to site/content/docs/v1.10/img/backup-process.png diff --git a/site/content/docs/v1.10.0-rc.1/img/gv_priority1-caseA.png b/site/content/docs/v1.10/img/gv_priority1-caseA.png similarity index 100% rename from site/content/docs/v1.10.0-rc.1/img/gv_priority1-caseA.png rename to site/content/docs/v1.10/img/gv_priority1-caseA.png diff --git a/site/content/docs/v1.10.0-rc.1/img/gv_priority1-caseB.png b/site/content/docs/v1.10/img/gv_priority1-caseB.png similarity index 100% rename from site/content/docs/v1.10.0-rc.1/img/gv_priority1-caseB.png rename to site/content/docs/v1.10/img/gv_priority1-caseB.png diff --git a/site/content/docs/v1.10.0-rc.1/img/gv_priority2-caseC.png b/site/content/docs/v1.10/img/gv_priority2-caseC.png similarity index 100% rename from site/content/docs/v1.10.0-rc.1/img/gv_priority2-caseC.png rename to site/content/docs/v1.10/img/gv_priority2-caseC.png diff --git a/site/content/docs/v1.10.0-rc.1/img/gv_priority3-caseD.png b/site/content/docs/v1.10/img/gv_priority3-caseD.png similarity index 100% rename from site/content/docs/v1.10.0-rc.1/img/gv_priority3-caseD.png rename to site/content/docs/v1.10/img/gv_priority3-caseD.png diff --git a/site/content/docs/v1.10.0-rc.1/img/velero.png b/site/content/docs/v1.10/img/velero.png similarity index 100% rename from site/content/docs/v1.10.0-rc.1/img/velero.png rename to site/content/docs/v1.10/img/velero.png diff --git a/site/content/docs/v1.10.0-rc.1/locations.md b/site/content/docs/v1.10/locations.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/locations.md rename to site/content/docs/v1.10/locations.md diff --git a/site/content/docs/v1.10.0-rc.1/maintainers.md b/site/content/docs/v1.10/maintainers.md similarity index 82% rename from site/content/docs/v1.10.0-rc.1/maintainers.md rename to site/content/docs/v1.10/maintainers.md index 866ad35d2f..cad29c63be 100644 --- a/site/content/docs/v1.10.0-rc.1/maintainers.md +++ b/site/content/docs/v1.10/maintainers.md @@ -4,7 +4,7 @@ layout: docs toc: "true" --- -There are some guidelines maintainers need to follow. We list them here for quick reference, especially for new maintainers. These guidelines apply to all projects in the Velero org, including the main project, the Velero Helm chart, and all other [related repositories](https://github.com/vmware-tanzu/velero/blob/v1.10.0-rc.1/GOVERNANCE.md#code-repositories). +There are some guidelines maintainers need to follow. We list them here for quick reference, especially for new maintainers. These guidelines apply to all projects in the Velero org, including the main project, the Velero Helm chart, and all other [related repositories](https://github.com/vmware-tanzu/velero/blob/v1.10.0/GOVERNANCE.md#code-repositories). Please be sure to also go through the guidance under the entire [Contribute](start-contributing/) section. @@ -14,12 +14,12 @@ Please be sure to also go through the guidance under the entire [Contribute](sta - As you review a PR that is not yet ready to merge, please check if the "request review" needs to be refreshed for any reviewer (this is better than @mention at them) - Refrain from @mention other maintainers to review the PR unless it is an immediate need. All maintainers already get notified through the automated add to the "request review". If it is an urgent need, please add a helpful message as to why it is so people can properly prioritize work. - There is no need to manually request reviewers: after the PR is created, all maintainers will be automatically added to the list (note: feel free to remove people if they are on PTO, etc). -- Be familiar with the [lazy consensus](https://github.com/vmware-tanzu/velero/blob/v1.10.0-rc.1/GOVERNANCE.md#lazy-consensus) policy for the project. +- Be familiar with the [lazy consensus](https://github.com/vmware-tanzu/velero/blob/v1.10.0/GOVERNANCE.md#lazy-consensus) policy for the project. Some tips for doing reviews: -- There are some [code standards and general guidelines](https://velero.io/docs/v1.10.0-rc.1/code-standards) we aim for -- We have [guidelines for writing and reviewing documentation](https://velero.io/docs/v1.10.0-rc.1/style-guide/) -- When reviewing a design document, ensure it follows [our format and guidelines]( https://github.com/vmware-tanzu/velero/blob/v1.10.0-rc.1/design/_template.md). Also, when reviewing a PR that implements a previously accepted design, ensure the associated design doc is moved to the [design/implemented](https://github.com/vmware-tanzu/velero/tree/main/design/implemented) folder. +- There are some [code standards and general guidelines](https://velero.io/docs/v1.10/code-standards) we aim for +- We have [guidelines for writing and reviewing documentation](https://velero.io/docs/v1.10/style-guide/) +- When reviewing a design document, ensure it follows [our format and guidelines]( https://github.com/vmware-tanzu/velero/blob/v1.10.0/design/_template.md). Also, when reviewing a PR that implements a previously accepted design, ensure the associated design doc is moved to the [design/implemented](https://github.com/vmware-tanzu/velero/tree/main/design/implemented) folder. ## Creating a release @@ -34,4 +34,4 @@ Maintainers are expected to participate in the community support rotation. We ha Maintainers for the Velero project are highly involved with the open source community. All the online community meetings for the project are listed in our [community](community) page. ## How do I become a maintainer? -The Velero project welcomes contributors of all kinds. We are also always on the look out for a high level of engagement from contributors and opportunities to bring in new maintainers. If this is of interest, take a look at how [adding a maintainer](https://github.com/vmware-tanzu/velero/blob/v1.10.0-rc.1/GOVERNANCE.md#maintainers) is decided. +The Velero project welcomes contributors of all kinds. We are also always on the look out for a high level of engagement from contributors and opportunities to bring in new maintainers. If this is of interest, take a look at how [adding a maintainer](https://github.com/vmware-tanzu/velero/blob/v1.10.0/GOVERNANCE.md#maintainers) is decided. diff --git a/site/content/docs/v1.10.0-rc.1/manual-testing.md b/site/content/docs/v1.10/manual-testing.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/manual-testing.md rename to site/content/docs/v1.10/manual-testing.md diff --git a/site/content/docs/v1.10.0-rc.1/migration-case.md b/site/content/docs/v1.10/migration-case.md similarity index 96% rename from site/content/docs/v1.10.0-rc.1/migration-case.md rename to site/content/docs/v1.10/migration-case.md index db6f4a066c..12c9166190 100644 --- a/site/content/docs/v1.10.0-rc.1/migration-case.md +++ b/site/content/docs/v1.10/migration-case.md @@ -42,7 +42,7 @@ This scenario steps through the migration of resources from Cluster 1 to Cluster velero backup create ``` - Alternatively, you can create a [scheduled backup](https://velero.io/docs/v1.10.0-rc.1/backup-reference/#schedule-a-backup) of your data with the Velero `schedule` operation. This is the recommended way to make sure your data is automatically backed up according to the schedule you define. + Alternatively, you can create a [scheduled backup](https://velero.io/docs/v1.10/backup-reference/#schedule-a-backup) of your data with the Velero `schedule` operation. This is the recommended way to make sure your data is automatically backed up according to the schedule you define. The default backup retention period, expressed as TTL (time to live), is 30 days (720 hours); you can use the `--ttl ` flag to change this as necessary. See [how velero works](how-velero-works.md#set-a-backup-to-expire) for more information about backup expiry. diff --git a/site/content/docs/v1.10.0-rc.1/namespace.md b/site/content/docs/v1.10/namespace.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/namespace.md rename to site/content/docs/v1.10/namespace.md diff --git a/site/content/docs/v1.10.0-rc.1/on-premises.md b/site/content/docs/v1.10/on-premises.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/on-premises.md rename to site/content/docs/v1.10/on-premises.md diff --git a/site/content/docs/v1.10.0-rc.1/output-file-format.md b/site/content/docs/v1.10/output-file-format.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/output-file-format.md rename to site/content/docs/v1.10/output-file-format.md diff --git a/site/content/docs/v1.10.0-rc.1/overview-plugins.md b/site/content/docs/v1.10/overview-plugins.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/overview-plugins.md rename to site/content/docs/v1.10/overview-plugins.md diff --git a/site/content/docs/v1.10/performance-guidance.md b/site/content/docs/v1.10/performance-guidance.md new file mode 100644 index 0000000000..dc8284ecf6 --- /dev/null +++ b/site/content/docs/v1.10/performance-guidance.md @@ -0,0 +1,163 @@ +--- +title: "Velero File System Backup Performance Guide" +layout: docs +--- + +When using Velero to do file system backup & restore, Restic uploader or Kopia uploader are both supported now. But the resources used and time consumption are a big difference between them. + +We've done series rounds of tests against Restic uploader and Kopia uploader through Velero, which may give you some guidance. But the test results will vary from different infrastructures, and our tests are limited and couldn't cover a variety of data scenarios, **the test results and analysis are for reference only**. + +## Infrastructure + +Minio is used as Velero backend storage, Network File System (NFS) is used to create the persistent volumes (PVs) and Persistent Volume Claims (PVC) based on the storage. The minio and NFS server are deployed independently in different virtual machines (VM), which with 300 MB/s write throughput and 175 MB/s read throughput representatively. + +The details of environmental information as below: + +``` +### KUBERNETES VERSION +root@velero-host-01:~# kubectl version +Client Version: version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.4" +Server Version: version.Info{Major:"1", Minor:"21", GitVersion:"v1.21.14" + +### DOCKER VERSION +root@velero-host-01:~# docker version +Client: + Version: 20.10.12 + API version: 1.41 + +Server: + Engine: + Version: 20.10.12 + API version: 1.41 (minimum version 1.12) + Go version: go1.16.2 + containerd: + Version: 1.5.9-0ubuntu1~20.04.4 + runc: + Version: 1.1.0-0ubuntu1~20.04.1 + docker-init: + Version: 0.19.0 + +### NODES +root@velero-host-01:~# kubectl get nodes |wc -l +6 // one master with 6 work nodes + +### DISK INFO +root@velero-host-01:~# smartctl -a /dev/sda +smartctl 7.1 2019-12-30 r5022 [x86_64-linux-5.4.0-126-generic] (local build) +Copyright (C) 2002-19, Bruce Allen, Christian Franke, www.smartmontools.org + +=== START OF INFORMATION SECTION === +Vendor: VMware +Product: Virtual disk +Revision: 1.0 +Logical block size: 512 bytes +Rotation Rate: Solid State Device +Device type: disk +### MEMORY INFO +root@velero-host-01:~# free -h + total used free shared buff/cache available +Mem: 3.8Gi 328Mi 3.1Gi 1.0Mi 469Mi 3.3Gi +Swap: 0B 0B 0B + +### CPU INFO +root@velero-host-01:~# cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c + 4 Intel(R) Xeon(R) Gold 6230R CPU @ 2.10GHz + +### SYSTEM INFO +root@velero-host-01:~# cat /proc/version +root@velero-host-01:~# cat /proc/version +Linux version 5.4.0-126-generic (build@lcy02-amd64-072) (gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1)) #142-Ubuntu SMP Fri Aug 26 12:12:57 UTC 2022 + +### VELERO VERSION +root@velero-host-01:~# velero version +Client: + Version: main ###v1.10 pre-release version + Git commit: 9b22ca6100646523876b18a491d881561b4dbcf3-dirty +Server: + Version: main ###v1.10 pre-release version +``` + +## Test + +Below we've done 6 groups of tests, for each single group of test, we used limited resources (1 core CPU 2 GB memory or 4 cores CPU 4 GB memory) to do Velero file system backup under Restic path and Kopia path, and then compare the results. + +Recorded the metrics of time consumption, maximum CPU usage, maximum memory usage, and minio storage usage for node-agent daemonset, and the metrics of Velero deployment are not included since the differences are not obvious by whether using Restic uploader or Kopia uploader. + +Compression is either disabled or not unavailable for both uploader. + +### Case 1: 4194304(4M) files, 2396745(2M) directories, 0B per file total 0B content +#### result: +|Uploader| Resources|Times |Max CPU|Max Memory|Repo Usage| +|--------|----------|:----:|------:|:--------:|:--------:| +| Kopia | 1c2g |24m54s| 65% |1530 MB |80 MB | +| Restic | 1c2g |52m31s| 55% |1708 MB |3.3 GB | +| Kopia | 4c4g |24m52s| 63% |2216 MB |80 MB | +| Restic | 4c4g |52m28s| 54% |2329 MB |3.3 GB | +#### conclusion: +- The memory usage is larger than Velero's default memory limit (1GB) for both Kopia and Restic under massive empty files. +- For both using Kopia uploader and Restic uploader, there is no significant time reduction by increasing resources from 1c2g to 4c4g. +- Restic uploader is one more time slower than Kopia uploader under the same specification resources. +- Restic has an **irrational** repository size (3.3GB) + +### Case 2: Using the same size (100B) of file and default Velero's resource configuration, the testing quantity of files from 20 thousand to 2 million, these groups of cases mainly test the behavior with the increasing quantity of files. + +### Case 2.1: 235298(23K) files, 137257 (10k)directories, 100B per file total 22.440MB content +#### result: +| Uploader | Resources|Times |Max CPU|Max Memory|Repo Usage| +|-------|----------|:----:|------:|:--------:|:--------:| +| Kopia | 1c1g |2m34s | 70% |692 MB |108 MB | +| Restic| 1c1g |3m9s | 54% |714 MB |275 MB | + +### Case 2.2 470596(40k) files, 137257 (10k)directories, 100B per file total 44.880MB content +#### result: +| Uploader | Resources|Times |Max CPU|Max Memory|Repo Usage| +|-------|----------|:----:|------:|:--------:|:--------:| +| Kopia | 1c1g |3m45s | 68% |831 MB |108 MB | +| Restic| 1c1g |4m53s | 57% |788 MB |275 MB | + +### Case 2.3 705894(70k) files, 137257(10k) directories, 100B per file total 67.319MB content +#### result: +|Uploader| Resources|Times |Max CPU|Max Memory|Repo Usage| +|--------|----------|:----:|------:|:--------:|:--------:| +| Kopia | 1c1g |5m06s | 71% |861 MB |108 MB | +| Restic | 1c1g |6m23s | 56% |810 MB |275 MB | + +### Case 2.4 2097152(2M) files, 2396745(2M) directories, 100B per file total 200.000MB content +#### result: +|Uploader| Resources|Times |Max CPU|Max Memory|Repo Usage| +|--------|----------|:----:|------:|:--------:|:--------:| +| Kopia | 1c1g |OOM | 74% |N/A |N/A | +| Restic | 1c1g |41m47s| 52% |904 MB |3.2 GB | +#### conclusion: +- With the increasing number of files, there is no memory abnormal surge, the memory usage for both Kopia uploader and Restic uploader is linear increasing, until exceeds 1GB memory usage in Case 2.4 Kopia uploader OOM happened. +- Kopia uploader gets increasingly faster along with the increasing number of files. +- Restic uploader repository size is still much larger than Kopia uploader repository. + +### Case 3: 10625(10k) files, 781 directories, 1.000MB per file total 10.376GB content +#### result: +|Uploader| Resources|Times |Max CPU|Max Memory|Repo Usage| +|--------|----------|:----:|------:|:--------:|:--------:| +| Kopia | 1c2g |1m37s | 75% |251 MB |10 GB | +| Restic | 1c2g |5m25s | 100% |153 MB |10 GB | +| Kopia | 4c4g |1m35s | 75% |248 MB |10 GB | +| Restic | 4c4g |3m17s | 171% |126 MB |10 GB | +#### conclusion: +- This case involves a relatively large backup size, there is no significant time reduction by increasing resources from 1c2g to 4c4g for Kopia uploader, but for Restic upoader when increasing CPU from 1 core to 4, backup time-consuming was shortened by one-third, which means in this scenario should allocate more CPU resources for Restic uploader. +- For the large backup size case, Restic uploader's repository size comes to normal + +### Case 4: 900 files, 1 directory, 1.000GB per file total 900.000GB content +#### result: +|Uploader| Resources|Times |Max CPU|Max Memory|Repo Usage| +|--------|----------|:-----:|------:|:--------:|:--------:| +| Kopia | 1c2g |2h30m | 100% |714 MB |900 GB | +| Restic | 1c2g |Timeout| 100% |416 MB |N/A | +| Kopia | 4c4g |1h42m | 138% |786 MB |900 GB | +| Restic | 4c4g |2h15m | 351% |606 MB |900 GB | +#### conclusion: +- When the target backup data is relatively large, Restic uploader starts to Timeout under 1c2g. So it's better to allocate more memory for Restic uploader when backup large sizes of data. +- For backup large amounts of data, Kopia uploader is both less time-consuming and less resource usage. + +## Summary +- With the same specification resources, Kopia uploader is less time-consuming when backup. +- Performance would be better if choosing Kopia uploader for the scenario in backup large mounts of data or massive small files. +- It's better to set one reasonable resource configuration instead of the default depending on your scenario. For default resource configuration, it's easy to be timeout with Restic uploader in backup large amounts of data, and it's easy to be OOM for both Kopia uploader and Restic uploader in backup of massive small files. \ No newline at end of file diff --git a/site/content/docs/v1.10.0-rc.1/plugin-release-instructions.md b/site/content/docs/v1.10/plugin-release-instructions.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/plugin-release-instructions.md rename to site/content/docs/v1.10/plugin-release-instructions.md diff --git a/site/content/docs/v1.10/proxy.md b/site/content/docs/v1.10/proxy.md new file mode 100644 index 0000000000..047a4ac4cb --- /dev/null +++ b/site/content/docs/v1.10/proxy.md @@ -0,0 +1,64 @@ +--- +title: "Behind Proxy" +layout: docs +toc: "true" +--- + +This document explains how to make Velero work behind proxy. +The procedures described in this document are concluded from the scenario that Velero is deployed behind proxy, and Velero needs to connect to a public MinIO server as storage location. Maybe other scenarios' configurations are not exactly the same, but basically they should share most parts. + +## Set the proxy server address +Specify the proxy server address by environment variables in Velero deployment and node-agent DaemonSet. +Take the following as an example: +``` yaml + ... + spec: + containers: + - args: + - server + - --features=EnableCSI + command: + - /velero + env: + ... + - name: HTTP_PROXY + value: + - name: HTTPS_PROXY + value: + # In case not all destinations that Velero connects to need go through proxy, users can specify the NO_PROXY to bypass proxy. + - name: NO_PROXY + value: +``` + +## Set the proxy required certificates +In some cases, the proxy requires certificate to connect. Set the certificate in the BSL's `Spec.ObjectStorage.CACert`. +It's possible that the object storage also requires certificate, and it's also set in `Spec.ObjectStorage.CACert`, then set both certificates in `Spec.ObjectStorage.CACert` field. + +The following is an example file contains two certificates, then encode its content with base64, and set the encode result in the BSL. + +``` bash +cat certs +-----BEGIN CERTIFICATE----- +certificates first content +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +certificates second content +-----END CERTIFICATE----- + +cat certs | base64 +LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCmNlcnRpZmljYXRlcyBmaXJzdCBjb250ZW50Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpjZXJ0aWZpY2F0ZXMgc2Vjb25kIGNvbnRlbnQKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= +``` + +``` yaml + apiVersion: velero.io/v1 + kind: BackupStorageLocation + ... + spec: + ... + default: true + objectStorage: + bucket: velero + caCert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCmNlcnRpZmljYXRlcyBmaXJzdCBjb250ZW50Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpjZXJ0aWZpY2F0ZXMgc2Vjb25kIGNvbnRlbnQKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + ... +``` diff --git a/site/content/docs/v1.10.0-rc.1/rbac.md b/site/content/docs/v1.10/rbac.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/rbac.md rename to site/content/docs/v1.10/rbac.md diff --git a/site/content/docs/v1.10.0-rc.1/release-instructions.md b/site/content/docs/v1.10/release-instructions.md similarity index 98% rename from site/content/docs/v1.10.0-rc.1/release-instructions.md rename to site/content/docs/v1.10/release-instructions.md index 62aab06336..615b035365 100644 --- a/site/content/docs/v1.10.0-rc.1/release-instructions.md +++ b/site/content/docs/v1.10/release-instructions.md @@ -9,7 +9,7 @@ This page covers the steps to perform when releasing a new version of Velero. - Please read the documented variables in each script to understand what they are for and how to properly format their values. - You will need to have an upstream remote configured to use to the [vmware-tanzu/velero](https://github.com/vmware-tanzu/velero) repository. You can check this using `git remote -v`. - The release script ([`tag-release.sh`](https://github.com/vmware-tanzu/velero/blob/v1.10.0-rc.1/hack/release-tools/tag-release.sh)) will use `upstream` as the default remote name if it is not specified using the environment variable `REMOTE`. + The release script ([`tag-release.sh`](https://github.com/vmware-tanzu/velero/blob/v1.10.0/hack/release-tools/tag-release.sh)) will use `upstream` as the default remote name if it is not specified using the environment variable `REMOTE`. - GA release: major and minor releases only. Example: 1.0 (major), 1.5 (minor). - Pre-releases: Any release leading up to a GA. Example: 1.4.0-beta.1, 1.5.0-rc.1 - RC releases: Release Candidate, contains everything that is supposed to ship with the GA release. This is still a pre-release. diff --git a/site/content/docs/v1.10.0-rc.1/release-schedule.md b/site/content/docs/v1.10/release-schedule.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/release-schedule.md rename to site/content/docs/v1.10/release-schedule.md diff --git a/site/content/docs/v1.10.0-rc.1/resource-filtering.md b/site/content/docs/v1.10/resource-filtering.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/resource-filtering.md rename to site/content/docs/v1.10/resource-filtering.md diff --git a/site/content/docs/v1.10.0-rc.1/restore-hooks.md b/site/content/docs/v1.10/restore-hooks.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/restore-hooks.md rename to site/content/docs/v1.10/restore-hooks.md diff --git a/site/content/docs/v1.10.0-rc.1/restore-reference.md b/site/content/docs/v1.10/restore-reference.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/restore-reference.md rename to site/content/docs/v1.10/restore-reference.md diff --git a/site/content/docs/v1.10.0-rc.1/run-locally.md b/site/content/docs/v1.10/run-locally.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/run-locally.md rename to site/content/docs/v1.10/run-locally.md diff --git a/site/content/docs/v1.10.0-rc.1/self-signed-certificates.md b/site/content/docs/v1.10/self-signed-certificates.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/self-signed-certificates.md rename to site/content/docs/v1.10/self-signed-certificates.md diff --git a/site/content/docs/v1.10.0-rc.1/start-contributing.md b/site/content/docs/v1.10/start-contributing.md similarity index 82% rename from site/content/docs/v1.10.0-rc.1/start-contributing.md rename to site/content/docs/v1.10/start-contributing.md index dfc324fa43..7f5857dbf0 100644 --- a/site/content/docs/v1.10.0-rc.1/start-contributing.md +++ b/site/content/docs/v1.10/start-contributing.md @@ -28,7 +28,7 @@ Please browse our list of resources, including a playlist of past online communi If you are ready to jump in and test, add code, or help with documentation, please use the navigation on the left under `Contribute`. -[1]: https://github.com/vmware-tanzu/velero/blob/v1.10.0-rc.1/CODE_OF_CONDUCT.md -[2]: https://github.com/vmware-tanzu/velero/blob/v1.10.0-rc.1/CONTRIBUTING.md -[3]: https://github.com/vmware-tanzu/velero/blob/v1.10.0-rc.1/.github/ISSUE_TEMPLATE/feature-enhancement-request.md -[4]: https://github.com/vmware-tanzu/velero/blob/v1.10.0-rc.1/.github/ISSUE_TEMPLATE/bug_report.md +[1]: https://github.com/vmware-tanzu/velero/blob/v1.10.0/CODE_OF_CONDUCT.md +[2]: https://github.com/vmware-tanzu/velero/blob/v1.10.0/CONTRIBUTING.md +[3]: https://github.com/vmware-tanzu/velero/blob/v1.10.0/.github/ISSUE_TEMPLATE/feature-enhancement-request.md +[4]: https://github.com/vmware-tanzu/velero/blob/v1.10.0/.github/ISSUE_TEMPLATE/bug_report.md diff --git a/site/content/docs/v1.10.0-rc.1/style-guide.md b/site/content/docs/v1.10/style-guide.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/style-guide.md rename to site/content/docs/v1.10/style-guide.md diff --git a/site/content/docs/v1.10.0-rc.1/support-process.md b/site/content/docs/v1.10/support-process.md similarity index 97% rename from site/content/docs/v1.10.0-rc.1/support-process.md rename to site/content/docs/v1.10/support-process.md index 8ef04d824f..bcc12afcdf 100644 --- a/site/content/docs/v1.10.0-rc.1/support-process.md +++ b/site/content/docs/v1.10/support-process.md @@ -6,7 +6,7 @@ layout: docs Velero provides best effort support through the process on this page for the current version of Velero and n-1 Velero version, including all patch releases in the supported minor releases. For example, if the current version is 1.9, the Velero maintainers would offer best effort support for v1.9 and v1.8. If you have a question about a previous Velero version (for example, 1.7), please note that maintainers may ask you to upgrade to a supported version before doing any investigation into your issue. -For more information about Velero testing and supported Kubernetes versions, see Velero's [compatibility matrix](https://github.com/vmware-tanzu/velero/blob/v1.10.0-rc.1/README.md#velero-compatibility-matrix). +For more information about Velero testing and supported Kubernetes versions, see Velero's [compatibility matrix](https://github.com/vmware-tanzu/velero/blob/v1.10.0/README.md#velero-compatibility-matrix). ## Weekly Rotation diff --git a/site/content/docs/v1.10.0-rc.1/supported-providers.md b/site/content/docs/v1.10/supported-providers.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/supported-providers.md rename to site/content/docs/v1.10/supported-providers.md diff --git a/site/content/docs/v1.10.0-rc.1/tilt.md b/site/content/docs/v1.10/tilt.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/tilt.md rename to site/content/docs/v1.10/tilt.md diff --git a/site/content/docs/v1.10.0-rc.1/troubleshooting.md b/site/content/docs/v1.10/troubleshooting.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/troubleshooting.md rename to site/content/docs/v1.10/troubleshooting.md diff --git a/site/content/docs/v1.10.0-rc.1/uninstalling.md b/site/content/docs/v1.10/uninstalling.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/uninstalling.md rename to site/content/docs/v1.10/uninstalling.md diff --git a/site/content/docs/v1.10.0-rc.1/upgrade-to-1.10.md b/site/content/docs/v1.10/upgrade-to-1.10.md similarity index 93% rename from site/content/docs/v1.10.0-rc.1/upgrade-to-1.10.md rename to site/content/docs/v1.10/upgrade-to-1.10.md index e0b23bb156..1eb8056ab0 100644 --- a/site/content/docs/v1.10.0-rc.1/upgrade-to-1.10.md +++ b/site/content/docs/v1.10/upgrade-to-1.10.md @@ -58,12 +58,12 @@ Before upgrading, check the [Velero compatibility matrix](https://github.com/vmw | kubectl apply -f - # optional, if using the restic daemon set - dsjson=$(kubectl get ds -n velero -ojson) - kubectl delete ds -n velero --all --force --grace-period 0 - echo $dsjson | sed "s#\"image\"\: \"velero\/velero\:v[0-9]*.[0-9]*.[0-9]\"#\"image\"\: \"velero\/velero\:v1.10.0\"#g" \ + echo $(kubectl get ds -n velero restic -ojson) \ + | sed "s#\"image\"\: \"velero\/velero\:v[0-9]*.[0-9]*.[0-9]\"#\"image\"\: \"velero\/velero\:v1.10.0\"#g" \ | sed "s#\"name\"\: \"restic\"#\"name\"\: \"node-agent\"#g" \ | sed "s#\[ \"restic\",#\[ \"node-agent\",#g" \ | kubectl apply -f - + kubectl delete ds -n velero restic --force --grace-period 0 ``` 1. Confirm that the deployment is up and running with the correct version by running: diff --git a/site/content/docs/v1.10.0-rc.1/velero-install.md b/site/content/docs/v1.10/velero-install.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/velero-install.md rename to site/content/docs/v1.10/velero-install.md diff --git a/site/content/docs/v1.10.0-rc.1/vendoring-dependencies.md b/site/content/docs/v1.10/vendoring-dependencies.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/vendoring-dependencies.md rename to site/content/docs/v1.10/vendoring-dependencies.md diff --git a/site/content/docs/v1.10.0-rc.1/website-guidelines.md b/site/content/docs/v1.10/website-guidelines.md similarity index 100% rename from site/content/docs/v1.10.0-rc.1/website-guidelines.md rename to site/content/docs/v1.10/website-guidelines.md diff --git a/site/data/docs/main-toc.yml b/site/data/docs/main-toc.yml index f8be3b9782..6d81fb3e90 100644 --- a/site/data/docs/main-toc.yml +++ b/site/data/docs/main-toc.yml @@ -51,6 +51,8 @@ toc: url: /self-signed-certificates - page: Changing RBAC permissions url: /rbac + - page: Behind proxy + url: /proxy - title: Plugins subfolderitems: - page: Overview diff --git a/site/data/docs/toc-mapping.yml b/site/data/docs/toc-mapping.yml index 83d84c7636..efd3d629d9 100644 --- a/site/data/docs/toc-mapping.yml +++ b/site/data/docs/toc-mapping.yml @@ -3,7 +3,7 @@ # that the navigation for older versions still work. main: main-toc -v1.10.0-rc.1: v1-10-0-rc-1-toc +v1.10: v1-10-toc v1.9: v1-9-toc v1.8: v1-8-toc v1.7: v1-7-toc diff --git a/site/data/docs/v1-10-0-rc-1-toc.yml b/site/data/docs/v1-10-toc.yml similarity index 98% rename from site/data/docs/v1-10-0-rc-1-toc.yml rename to site/data/docs/v1-10-toc.yml index f8be3b9782..6d81fb3e90 100644 --- a/site/data/docs/v1-10-0-rc-1-toc.yml +++ b/site/data/docs/v1-10-toc.yml @@ -51,6 +51,8 @@ toc: url: /self-signed-certificates - page: Changing RBAC permissions url: /rbac + - page: Behind proxy + url: /proxy - title: Plugins subfolderitems: - page: Overview diff --git a/test/e2e/backup/backup.go b/test/e2e/backup/backup.go index 5507737180..815260369c 100644 --- a/test/e2e/backup/backup.go +++ b/test/e2e/backup/backup.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/test/e2e/backups/deletion.go b/test/e2e/backups/deletion.go index db1313c5ad..bf11e8a725 100644 --- a/test/e2e/backups/deletion.go +++ b/test/e2e/backups/deletion.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/test/e2e/backups/sync_backups.go b/test/e2e/backups/sync_backups.go index deb9befa64..8420b76afb 100644 --- a/test/e2e/backups/sync_backups.go +++ b/test/e2e/backups/sync_backups.go @@ -13,10 +13,10 @@ * 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. - * / + * */ -//Refer to https://github.com/vmware-tanzu/velero/issues/4253 +// Refer to https://github.com/vmware-tanzu/velero/issues/4253 package backups import ( diff --git a/test/e2e/basic/enable_api_group_versions.go b/test/e2e/basic/enable_api_group_versions.go index 862854a02c..cf6db22d7f 100644 --- a/test/e2e/basic/enable_api_group_versions.go +++ b/test/e2e/basic/enable_api_group_versions.go @@ -115,6 +115,7 @@ func APIExtensionsVersionsTest() { By(fmt.Sprintf("Install CRD of apiextenstions v1beta1 in cluster-A (%s)", VeleroCfg.DefaultCluster), func() { Expect(installCRD(context.Background(), srcCrdYaml)).To(Succeed()) Expect(CRDShouldExist(context.Background(), crdName)).To(Succeed()) + Expect(WaitForCRDEstablished(crdName)).To(Succeed()) Expect(AddLabelToCRD(context.Background(), crdName, label)).To(Succeed()) }) diff --git a/test/e2e/bsl-mgmt/deletion.go b/test/e2e/bsl-mgmt/deletion.go index 20b591d73f..2a74976c6f 100644 --- a/test/e2e/bsl-mgmt/deletion.go +++ b/test/e2e/bsl-mgmt/deletion.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index 0eb3697145..be0cda0105 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -33,15 +33,13 @@ import ( . "github.com/vmware-tanzu/velero/test/e2e/basic" . "github.com/vmware-tanzu/velero/test/e2e/basic/resources-check" . "github.com/vmware-tanzu/velero/test/e2e/bsl-mgmt" - . "github.com/vmware-tanzu/velero/test/e2e/orderedresources" + . "github.com/vmware-tanzu/velero/test/e2e/migration" . "github.com/vmware-tanzu/velero/test/e2e/privilegesmgmt" . "github.com/vmware-tanzu/velero/test/e2e/pv-backup" . "github.com/vmware-tanzu/velero/test/e2e/resource-filtering" - . "github.com/vmware-tanzu/velero/test/e2e/scale" + . "github.com/vmware-tanzu/velero/test/e2e/schedule" . "github.com/vmware-tanzu/velero/test/e2e/upgrade" - - . "github.com/vmware-tanzu/velero/test/e2e/migration" . "github.com/vmware-tanzu/velero/test/e2e/util/k8s" ) @@ -114,7 +112,9 @@ var _ = Describe("[Backups][Deletion][Restic] Velero tests of Restic backup dele var _ = Describe("[Backups][Deletion][Snapshot] Velero tests of snapshot backup deletion", BackupDeletionWithSnapshots) var _ = Describe("[Backups][TTL] Local backups and restic repos will be deleted once the corresponding backup storage location is deleted", TTLTest) var _ = Describe("[Backups][BackupsSync] Backups in object storage are synced to a new Velero and deleted backups in object storage are synced to be deleted in Velero", BackupsSyncTest) -var _ = Describe("[Backups][Schedule] Backup will be created periodly by schedule defined by a Cron expression", ScheduleBackupTest) + +var _ = Describe("[Schedule][BR][Pause] Backup will be created periodly by schedule defined by a Cron expression", ScheduleBackupTest) +var _ = Describe("[Schedule][OrederedResources] Backup resources should follow the specific order in schedule", ScheduleOrderedResources) var _ = Describe("[PrivilegesMgmt][SSR] Velero test on ssr object when controller namespace mix-ups", SSRTest) @@ -125,8 +125,6 @@ var _ = Describe("[Migration][Restic]", MigrationWithRestic) var _ = Describe("[Migration][Snapshot]", MigrationWithSnapshots) -var _ = Describe("[Schedule][OrederedResources] Backup resources should follow the specific order in schedule", ScheduleOrderedResources) - var _ = Describe("[NamespaceMapping][Single][Restic] Backup resources should follow the specific order in schedule", OneNamespaceMappingResticTest) var _ = Describe("[NamespaceMapping][Multiple][Restic] Backup resources should follow the specific order in schedule", MultiNamespacesMappingResticTest) var _ = Describe("[NamespaceMapping][Single][Snapshot] Backup resources should follow the specific order in schedule", OneNamespaceMappingSnapshotTest) diff --git a/test/e2e/migration/migration.go b/test/e2e/migration/migration.go index 8b163b111a..fab95fa0cb 100644 --- a/test/e2e/migration/migration.go +++ b/test/e2e/migration/migration.go @@ -5,7 +5,7 @@ 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 +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, @@ -98,7 +98,7 @@ func MigrationTest(useVolumeSnapshots bool, veleroCLI2Version VeleroCLI2Version) UUIDgen, err = uuid.NewRandom() Expect(err).To(Succeed()) - oneHourTimeout, _ := context.WithTimeout(context.Background(), time.Minute*10) + oneHourTimeout, _ := context.WithTimeout(context.Background(), time.Minute*60) if veleroCLI2Version.VeleroCLI == "" { //Assume tag of velero server image is identical to velero CLI version diff --git a/test/e2e/pkg/client/auth_providers.go b/test/e2e/pkg/client/auth_providers.go new file mode 100644 index 0000000000..c12f842c15 --- /dev/null +++ b/test/e2e/pkg/client/auth_providers.go @@ -0,0 +1,25 @@ +/* +Copyright 2017 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 client + +// Make sure we import the client-go auth provider plugins. + +import ( + _ "k8s.io/client-go/plugin/pkg/client/auth/azure" + _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" +) diff --git a/test/e2e/pkg/client/client.go b/test/e2e/pkg/client/client.go new file mode 100644 index 0000000000..e32533cfbf --- /dev/null +++ b/test/e2e/pkg/client/client.go @@ -0,0 +1,70 @@ +/* +Copyright 2017, 2019 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 client + +import ( + "fmt" + "runtime" + + "github.com/pkg/errors" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + + "github.com/vmware-tanzu/velero/pkg/buildinfo" +) + +func buildConfigFromFlags(context, kubeconfigPath string, precedence []string) (*rest.Config, error) { + return clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfigPath, Precedence: precedence}, + &clientcmd.ConfigOverrides{ + CurrentContext: context, + }).ClientConfig() +} + +// Config returns a *rest.Config, using either the kubeconfig (if specified) or an in-cluster +// configuration. +func Config(kubeconfig, kubecontext, baseName string, qps float32, burst int) (*rest.Config, error) { + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + loadingRules.ExplicitPath = kubeconfig + clientConfig, err := buildConfigFromFlags(kubecontext, kubeconfig, loadingRules.Precedence) + if err != nil { + return nil, errors.Wrap(err, "error finding Kubernetes API server config in --kubeconfig, $KUBECONFIG, or in-cluster configuration") + } + + if qps > 0.0 { + clientConfig.QPS = qps + } + if burst > 0 { + clientConfig.Burst = burst + } + + clientConfig.UserAgent = buildUserAgent( + baseName, + buildinfo.Version, + buildinfo.FormattedGitSHA(), + runtime.GOOS, + runtime.GOARCH, + ) + + return clientConfig, nil +} + +// buildUserAgent builds a User-Agent string from given args. +func buildUserAgent(command, version, formattedSha, os, arch string) string { + return fmt.Sprintf( + "%s/%s (%s/%s) %s", command, version, os, arch, formattedSha) +} diff --git a/test/e2e/pkg/client/client_test.go b/test/e2e/pkg/client/client_test.go new file mode 100644 index 0000000000..df8df51e43 --- /dev/null +++ b/test/e2e/pkg/client/client_test.go @@ -0,0 +1,51 @@ +/* +Copyright 2018 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 client + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBuildUserAgent(t *testing.T) { + tests := []struct { + name string + command string + os string + arch string + gitSha string + version string + expected string + }{ + { + name: "Test general interpolation in correct order", + command: "velero", + os: "darwin", + arch: "amd64", + gitSha: "abc123", + version: "v0.1.1", + expected: "velero/v0.1.1 (darwin/amd64) abc123", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + resp := buildUserAgent(test.command, test.version, test.gitSha, test.os, test.arch) + assert.Equal(t, resp, test.expected) + }) + } +} diff --git a/test/e2e/pkg/client/config.go b/test/e2e/pkg/client/config.go new file mode 100644 index 0000000000..2302ca9c91 --- /dev/null +++ b/test/e2e/pkg/client/config.go @@ -0,0 +1,151 @@ +/* +Copyright 2021 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 client + +import ( + "encoding/json" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/pkg/errors" +) + +const ( + ConfigKeyNamespace = "namespace" + ConfigKeyFeatures = "features" + ConfigKeyCACert = "cacert" + ConfigKeyColorized = "colorized" +) + +// VeleroConfig is a map of strings to interface{} for deserializing Velero client config options. +// The alias is a way to attach type-asserting convenience methods. +type VeleroConfig map[string]interface{} + +// LoadConfig loads the Velero client configuration file and returns it as a VeleroConfig. If the +// file does not exist, an empty map is returned. +func LoadConfig() (VeleroConfig, error) { + fileName := configFileName() + + _, err := os.Stat(fileName) + if os.IsNotExist(err) { + // If the file isn't there, just return an empty map + return VeleroConfig{}, nil + } + if err != nil { + // For any other Stat() error, return it + return nil, errors.WithStack(err) + } + + configFile, err := os.Open(fileName) + if err != nil { + return nil, errors.WithStack(err) + } + defer configFile.Close() + + var config VeleroConfig + if err := json.NewDecoder(configFile).Decode(&config); err != nil { + return nil, errors.WithStack(err) + } + + return config, nil +} + +// SaveConfig saves the passed in config map to the Velero client configuration file. +func SaveConfig(config VeleroConfig) error { + fileName := configFileName() + + // Try to make the directory in case it doesn't exist + dir := filepath.Dir(fileName) + if err := os.MkdirAll(dir, 0700); err != nil { + return errors.WithStack(err) + } + + configFile, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) + if err != nil { + return errors.WithStack(err) + } + defer configFile.Close() + + return json.NewEncoder(configFile).Encode(&config) +} + +func (c VeleroConfig) Namespace() string { + val, ok := c[ConfigKeyNamespace] + if !ok { + return "" + } + + ns, ok := val.(string) + if !ok { + return "" + } + + return ns +} + +func (c VeleroConfig) Features() []string { + val, ok := c[ConfigKeyFeatures] + if !ok { + return []string{} + } + + features, ok := val.(string) + if !ok { + return []string{} + } + + return strings.Split(features, ",") +} + +func (c VeleroConfig) Colorized() bool { + val, ok := c[ConfigKeyColorized] + if !ok { + return true + } + + valString, ok := val.(string) + if !ok { + return true + } + + colorized, err := strconv.ParseBool(valString) + if err != nil { + return true + } + + return colorized + +} + +func (c VeleroConfig) CACertFile() string { + val, ok := c[ConfigKeyCACert] + if !ok { + return "" + } + caCertFile, ok := val.(string) + if !ok { + return "" + } + + return caCertFile +} + +func configFileName() string { + return filepath.Join(os.Getenv("HOME"), ".config", "velero", "config.json") +} diff --git a/test/e2e/pkg/client/config_test.go b/test/e2e/pkg/client/config_test.go new file mode 100644 index 0000000000..e78a9b116c --- /dev/null +++ b/test/e2e/pkg/client/config_test.go @@ -0,0 +1,34 @@ +/* +Copyright 2021 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 client + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestVeleroConfig(t *testing.T) { + c := VeleroConfig{ + "namespace": "foo", + "features": "feature1,feature2", + } + + assert.Equal(t, "foo", c.Namespace()) + assert.Equal(t, []string{"feature1", "feature2"}, c.Features()) + assert.Equal(t, true, c.Colorized()) +} diff --git a/test/e2e/pkg/client/dynamic.go b/test/e2e/pkg/client/dynamic.go new file mode 100644 index 0000000000..8fcfab107b --- /dev/null +++ b/test/e2e/pkg/client/dynamic.go @@ -0,0 +1,141 @@ +/* +Copyright 2017 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 client + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/dynamic" +) + +// DynamicFactory contains methods for retrieving dynamic clients for GroupVersionResources and +// GroupVersionKinds. +type DynamicFactory interface { + // ClientForGroupVersionResource returns a Dynamic client for the given group/version + // and resource for the given namespace. + ClientForGroupVersionResource(gv schema.GroupVersion, resource metav1.APIResource, namespace string) (Dynamic, error) +} + +// dynamicFactory implements DynamicFactory. +type dynamicFactory struct { + dynamicClient dynamic.Interface +} + +// NewDynamicFactory returns a new ClientPool-based dynamic factory. +func NewDynamicFactory(dynamicClient dynamic.Interface) DynamicFactory { + return &dynamicFactory{dynamicClient: dynamicClient} +} + +func (f *dynamicFactory) ClientForGroupVersionResource(gv schema.GroupVersion, resource metav1.APIResource, namespace string) (Dynamic, error) { + return &dynamicResourceClient{ + resourceClient: f.dynamicClient.Resource(gv.WithResource(resource.Name)).Namespace(namespace), + }, nil +} + +// Creator creates an object. +type Creator interface { + // Create creates an object. + Create(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) +} + +// Lister lists objects. +type Lister interface { + // List lists all the objects of a given resource. + List(metav1.ListOptions) (*unstructured.UnstructuredList, error) +} + +// Watcher watches objects. +type Watcher interface { + // Watch watches for changes to objects of a given resource. + Watch(metav1.ListOptions) (watch.Interface, error) +} + +// Getter gets an object. +type Getter interface { + // Get fetches an object by name. + Get(name string, opts metav1.GetOptions) (*unstructured.Unstructured, error) +} + +// Patcher patches an object. +type Patcher interface { + //Patch patches the named object using the provided patch bytes, which are expected to be in JSON merge patch format. The patched object is returned. + + Patch(name string, data []byte) (*unstructured.Unstructured, error) +} + +// Deletor deletes an object. +type Deletor interface { + //Patch patches the named object using the provided patch bytes, which are expected to be in JSON merge patch format. The patched object is returned. + + Delete(name string, opts metav1.DeleteOptions) error +} + +// StatusUpdater updates status field of a object +type StatusUpdater interface { + UpdateStatus(obj *unstructured.Unstructured, opts metav1.UpdateOptions) (*unstructured.Unstructured, error) +} + +// Dynamic contains client methods that Velero needs for backing up and restoring resources. +type Dynamic interface { + Creator + Lister + Watcher + Getter + Patcher + Deletor + StatusUpdater +} + +// dynamicResourceClient implements Dynamic. +type dynamicResourceClient struct { + resourceClient dynamic.ResourceInterface +} + +var _ Dynamic = &dynamicResourceClient{} + +func (d *dynamicResourceClient) Create(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { + return d.resourceClient.Create(context.TODO(), obj, metav1.CreateOptions{}) +} + +func (d *dynamicResourceClient) List(options metav1.ListOptions) (*unstructured.UnstructuredList, error) { + return d.resourceClient.List(context.TODO(), options) +} + +func (d *dynamicResourceClient) Watch(options metav1.ListOptions) (watch.Interface, error) { + return d.resourceClient.Watch(context.TODO(), options) +} + +func (d *dynamicResourceClient) Get(name string, opts metav1.GetOptions) (*unstructured.Unstructured, error) { + return d.resourceClient.Get(context.TODO(), name, opts) +} + +func (d *dynamicResourceClient) Patch(name string, data []byte) (*unstructured.Unstructured, error) { + return d.resourceClient.Patch(context.TODO(), name, types.MergePatchType, data, metav1.PatchOptions{}) +} + +func (d *dynamicResourceClient) Delete(name string, opts metav1.DeleteOptions) error { + return d.resourceClient.Delete(context.TODO(), name, opts) +} + +func (d *dynamicResourceClient) UpdateStatus(obj *unstructured.Unstructured, opts metav1.UpdateOptions) (*unstructured.Unstructured, error) { + return d.resourceClient.UpdateStatus(context.TODO(), obj, opts) +} diff --git a/test/e2e/pkg/client/factory.go b/test/e2e/pkg/client/factory.go new file mode 100644 index 0000000000..3a0933af79 --- /dev/null +++ b/test/e2e/pkg/client/factory.go @@ -0,0 +1,185 @@ +/* +Copyright 2017, 2019 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 client + +import ( + "os" + + apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + k8scheme "k8s.io/client-go/kubernetes/scheme" + kbclient "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/pkg/errors" + "github.com/spf13/pflag" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + + velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + clientset "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned" +) + +// Factory knows how to create a VeleroClient and Kubernetes client. +type Factory interface { + // BindFlags binds common flags (--kubeconfig, --namespace) to the passed-in FlagSet. + BindFlags(flags *pflag.FlagSet) + // Client returns a VeleroClient. It uses the following priority to specify the cluster + // configuration: --kubeconfig flag, KUBECONFIG environment variable, in-cluster configuration. + Client() (clientset.Interface, error) + // KubeClient returns a Kubernetes client. It uses the following priority to specify the cluster + // configuration: --kubeconfig flag, KUBECONFIG environment variable, in-cluster configuration. + KubeClient() (kubernetes.Interface, error) + // DynamicClient returns a Kubernetes dynamic client. It uses the following priority to specify the cluster + // configuration: --kubeconfig flag, KUBECONFIG environment variable, in-cluster configuration. + DynamicClient() (dynamic.Interface, error) + // KubebuilderClient returns a client for the controller runtime framework. It adds Kubernetes and Velero + // types to its scheme. It uses the following priority to specify the cluster + // configuration: --kubeconfig flag, KUBECONFIG environment variable, in-cluster configuration. + KubebuilderClient() (kbclient.Client, error) + // SetBasename changes the basename for an already-constructed client. + // This is useful for generating clients that require a different user-agent string below the root `velero` + // command, such as the server subcommand. + SetBasename(string) + // SetClientQPS sets the Queries Per Second for a client. + SetClientQPS(float32) + // SetClientBurst sets the Burst for a client. + SetClientBurst(int) + // ClientConfig returns a rest.Config struct used for client-go clients. + ClientConfig() (*rest.Config, error) + // Namespace returns the namespace which the Factory will create clients for. + Namespace() string +} + +type factory struct { + flags *pflag.FlagSet + kubeconfig string + kubecontext string + baseName string + namespace string + clientQPS float32 + clientBurst int +} + +// NewFactory returns a Factory. +func NewFactory(baseName, kubecontext string, config VeleroConfig) Factory { + f := &factory{ + flags: pflag.NewFlagSet("", pflag.ContinueOnError), + baseName: baseName, + kubecontext: kubecontext, + } + + f.namespace = os.Getenv("VELERO_NAMESPACE") + if config.Namespace() != "" { + f.namespace = config.Namespace() + } + + // We didn't get the namespace via env var or config file, so use the default. + // Command line flags will override when BindFlags is called. + if f.namespace == "" { + f.namespace = velerov1api.DefaultNamespace + } + + f.flags.StringVar(&f.kubeconfig, "kubeconfig", "", "Path to the kubeconfig file to use to talk to the Kubernetes apiserver. If unset, try the environment variable KUBECONFIG, as well as in-cluster configuration") + f.flags.StringVarP(&f.namespace, "namespace", "n", f.namespace, "The namespace in which Velero should operate") + //f.flags.StringVar(&f.kubecontext, "kubecontext", "", "The context to use to talk to the Kubernetes apiserver. If unset defaults to whatever your current-context is (kubectl config current-context)") + return f +} + +func (f *factory) BindFlags(flags *pflag.FlagSet) { + flags.AddFlagSet(f.flags) +} + +func (f *factory) ClientConfig() (*rest.Config, error) { + return Config(f.kubeconfig, f.kubecontext, f.baseName, f.clientQPS, f.clientBurst) +} + +func (f *factory) Client() (clientset.Interface, error) { + clientConfig, err := f.ClientConfig() + if err != nil { + return nil, err + } + + veleroClient, err := clientset.NewForConfig(clientConfig) + if err != nil { + return nil, errors.WithStack(err) + } + return veleroClient, nil +} + +func (f *factory) KubeClient() (kubernetes.Interface, error) { + clientConfig, err := f.ClientConfig() + if err != nil { + return nil, err + } + kubeClient, err := kubernetes.NewForConfig(clientConfig) + if err != nil { + return nil, errors.WithStack(err) + } + return kubeClient, nil +} + +func (f *factory) DynamicClient() (dynamic.Interface, error) { + clientConfig, err := f.ClientConfig() + if err != nil { + return nil, err + } + dynamicClient, err := dynamic.NewForConfig(clientConfig) + if err != nil { + return nil, errors.WithStack(err) + } + return dynamicClient, nil +} + +func (f *factory) KubebuilderClient() (kbclient.Client, error) { + clientConfig, err := f.ClientConfig() + if err != nil { + return nil, err + } + + scheme := runtime.NewScheme() + velerov1api.AddToScheme(scheme) + k8scheme.AddToScheme(scheme) + apiextv1beta1.AddToScheme(scheme) + apiextv1.AddToScheme(scheme) + kubebuilderClient, err := kbclient.New(clientConfig, kbclient.Options{ + Scheme: scheme, + }) + + if err != nil { + return nil, err + } + + return kubebuilderClient, nil +} + +func (f *factory) SetBasename(name string) { + f.baseName = name +} + +func (f *factory) SetClientQPS(qps float32) { + f.clientQPS = qps +} + +func (f *factory) SetClientBurst(burst int) { + f.clientBurst = burst +} + +func (f *factory) Namespace() string { + return f.namespace +} diff --git a/test/e2e/pkg/client/factory_test.go b/test/e2e/pkg/client/factory_test.go new file mode 100644 index 0000000000..d257b88b57 --- /dev/null +++ b/test/e2e/pkg/client/factory_test.go @@ -0,0 +1,61 @@ +/* +Copyright 2019 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 client + +import ( + "os" + "testing" + + "github.com/spf13/pflag" + "github.com/stretchr/testify/assert" +) + +// TestFactory tests the client.Factory interface. +func TestFactory(t *testing.T) { + // Velero client configuration is currently omitted due to requiring a + // test filesystem in pkg/test. This causes an import cycle as pkg/test + // uses pkg/client's interfaces to implement fakes + + // Env variable should set the namespace if no config or argument are used + os.Setenv("VELERO_NAMESPACE", "env-velero") + f := NewFactory("velero", "", make(map[string]interface{})) + + assert.Equal(t, "env-velero", f.Namespace()) + + os.Unsetenv("VELERO_NAMESPACE") + + // Argument should change the namespace + f = NewFactory("velero", "", make(map[string]interface{})) + s := "flag-velero" + flags := new(pflag.FlagSet) + + f.BindFlags(flags) + + flags.Parse([]string{"--namespace", s}) + + assert.Equal(t, s, f.Namespace()) + + // An argument overrides the env variable if both are set. + os.Setenv("VELERO_NAMESPACE", "env-velero") + f = NewFactory("velero", "", make(map[string]interface{})) + flags = new(pflag.FlagSet) + + f.BindFlags(flags) + flags.Parse([]string{"--namespace", s}) + assert.Equal(t, s, f.Namespace()) + + os.Unsetenv("VELERO_NAMESPACE") +} diff --git a/test/e2e/pv-backup/pv-backup-filter.go b/test/e2e/pv-backup/pv-backup-filter.go index 4cef609d3a..a531ee149c 100644 --- a/test/e2e/pv-backup/pv-backup-filter.go +++ b/test/e2e/pv-backup/pv-backup-filter.go @@ -115,7 +115,7 @@ func (p *PVBackupFiltering) CreateResources() error { }) } }) - By(fmt.Sprintf("Polulate all pods %s with file %s", p.podsList, FILE_NAME), func() { + By(fmt.Sprintf("Populate all pods %s with file %s", p.podsList, FILE_NAME), func() { for index, ns := range *p.NSIncluded { By(fmt.Sprintf("Creating file in all pods to start %d in namespace %s", index, ns), func() { WaitForPods(p.Ctx, p.Client, ns, p.podsList[index]) diff --git a/test/e2e/orderedresources/ordered_resources.go b/test/e2e/schedule/ordered_resources.go similarity index 99% rename from test/e2e/orderedresources/ordered_resources.go rename to test/e2e/schedule/ordered_resources.go index 44669cdc0c..a95d04a541 100644 --- a/test/e2e/orderedresources/ordered_resources.go +++ b/test/e2e/schedule/ordered_resources.go @@ -1,4 +1,4 @@ -package orderedresources +package schedule /* Copyright the Velero contributors. diff --git a/test/e2e/backups/schedule.go b/test/e2e/schedule/schedule.go similarity index 70% rename from test/e2e/backups/schedule.go rename to test/e2e/schedule/schedule.go index a30ba761e6..0b294221f0 100644 --- a/test/e2e/backups/schedule.go +++ b/test/e2e/schedule/schedule.go @@ -1,4 +1,4 @@ -package backups +package schedule import ( "context" @@ -25,11 +25,11 @@ type ScheduleBackup struct { verifyTimes int } -var ScheduleBackupTest func() = TestFunc(&ScheduleBackup{TestCase: TestCase{NSBaseName: "schedule-test-ns", NSIncluded: &[]string{"ns1"}}}) +var ScheduleBackupTest func() = TestFunc(&ScheduleBackup{TestCase: TestCase{NSBaseName: "schedule-test"}}) func (n *ScheduleBackup) Init() error { n.Client = TestClientInstance - n.Period = 3 + n.Period = 3 // Unit is minute n.verifyTimes = 5 // More verify times more confidence n.TestMsg = &TestMSG{ Desc: "Set up a scheduled backup defined by a Cron expression", @@ -40,7 +40,7 @@ func (n *ScheduleBackup) Init() error { } func (n *ScheduleBackup) StartRun() error { - + n.NSIncluded = &[]string{fmt.Sprintf("%s-%s", n.NSBaseName, "ns")} n.ScheduleName = n.ScheduleName + "schedule-" + UUIDgen.String() n.RestoreName = n.RestoreName + "restore-ns-mapping-" + UUIDgen.String() @@ -48,7 +48,7 @@ func (n *ScheduleBackup) StartRun() error { "--include-namespaces", strings.Join(*n.NSIncluded, ","), "--schedule=*/" + fmt.Sprintf("%v", n.Period) + " * * * *", } - + Expect(n.Period < 30).To(Equal(true)) return nil } func (n *ScheduleBackup) CreateResources() error { @@ -152,6 +152,53 @@ func (n *ScheduleBackup) Destroy() error { "--wait", } + backupsInfo, err := GetScheduledBackupsCreationTime(context.Background(), VeleroCfg.VeleroCLI, "default", n.ScheduleName) + Expect(err).To(Succeed(), fmt.Sprintf("Fail to get backups from schedule %s", n.ScheduleName)) + fmt.Println(backupsInfo) + backupCount := len(backupsInfo) + + By(fmt.Sprintf("Pause schedule %s ......\n", n.ScheduleName), func() { + Expect(VeleroSchedulePause(n.Ctx, VeleroCfg.VeleroCLI, VeleroCfg.VeleroNamespace, n.ScheduleName)).To(Succeed(), func() string { + RunDebug(context.Background(), VeleroCfg.VeleroCLI, VeleroCfg.VeleroNamespace, "", "") + return "Fail to restore workload" + }) + }) + + periodCount := 3 + sleepDuration := time.Duration(n.Period*periodCount) * time.Minute + By(fmt.Sprintf("Sleep for %s ......\n", sleepDuration), func() { + time.Sleep(sleepDuration) + }) + + backupsInfo, err = GetScheduledBackupsCreationTime(context.Background(), VeleroCfg.VeleroCLI, "default", n.ScheduleName) + Expect(err).To(Succeed(), fmt.Sprintf("Fail to get backups from schedule %s", n.ScheduleName)) + + backupCountPostPause := len(backupsInfo) + fmt.Printf("After pause, backkups count is %d\n", backupCountPostPause) + + By(fmt.Sprintf("Verify no new backups from %s ......\n", n.ScheduleName), func() { + Expect(backupCountPostPause == backupCount).To(Equal(true)) + }) + + By(fmt.Sprintf("Unpause schedule %s ......\n", n.ScheduleName), func() { + Expect(VeleroScheduleUnpause(n.Ctx, VeleroCfg.VeleroCLI, VeleroCfg.VeleroNamespace, n.ScheduleName)).To(Succeed(), func() string { + RunDebug(context.Background(), VeleroCfg.VeleroCLI, VeleroCfg.VeleroNamespace, "", "") + return "Fail to unpause schedule" + }) + }) + + By(fmt.Sprintf("Sleep for %s ......\n", sleepDuration), func() { + time.Sleep(sleepDuration) + }) + + backupsInfo, err = GetScheduledBackupsCreationTime(context.Background(), VeleroCfg.VeleroCLI, "default", n.ScheduleName) + Expect(err).To(Succeed(), fmt.Sprintf("Fail to get backups from schedule %s", n.ScheduleName)) + fmt.Println(backupsInfo) + backupCountPostUnpause := len(backupsInfo) + fmt.Printf("After unpause, backkups count is %d\n", backupCountPostUnpause) + By(fmt.Sprintf("Verify no new backups by schedule %s ......\n", n.ScheduleName), func() { + Expect(backupCountPostUnpause-backupCount >= periodCount-1).To(Equal(true)) + }) return nil } diff --git a/test/e2e/upgrade/upgrade.go b/test/e2e/upgrade/upgrade.go index ac627fae48..33eeb42fd3 100644 --- a/test/e2e/upgrade/upgrade.go +++ b/test/e2e/upgrade/upgrade.go @@ -5,7 +5,7 @@ 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 +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, diff --git a/test/e2e/util/k8s/client.go b/test/e2e/util/k8s/client.go index 4601bae8fc..0db9d3aaa5 100644 --- a/test/e2e/util/k8s/client.go +++ b/test/e2e/util/k8s/client.go @@ -20,7 +20,7 @@ import ( "k8s.io/client-go/kubernetes" kbclient "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/vmware-tanzu/velero/pkg/client" + "github.com/vmware-tanzu/velero/test/e2e/pkg/client" ) // TestClient contains different API clients that are in use throughout diff --git a/test/e2e/util/k8s/common.go b/test/e2e/util/k8s/common.go index 7b2d75edb3..6ea041dfdf 100644 --- a/test/e2e/util/k8s/common.go +++ b/test/e2e/util/k8s/common.go @@ -327,3 +327,17 @@ func DeleteVeleroDs(ctx context.Context) error { fmt.Println(args) return exec.CommandContext(ctx, "kubectl", args...).Run() } + +func WaitForCRDEstablished(crdName string) error { + arg := []string{"wait", "--for", "condition=established", "--timeout=60s", "crd/" + crdName} + cmd := exec.CommandContext(context.Background(), "kubectl", arg...) + fmt.Printf("Kubectl exec cmd =%v\n", cmd) + stdout, stderr, err := veleroexec.RunCommand(cmd) + fmt.Println(stdout) + if err != nil { + fmt.Println(stderr) + fmt.Println(err) + return err + } + return nil +} diff --git a/test/e2e/util/k8s/namespace.go b/test/e2e/util/k8s/namespace.go index 767aba7ba5..1049666094 100644 --- a/test/e2e/util/k8s/namespace.go +++ b/test/e2e/util/k8s/namespace.go @@ -35,6 +35,11 @@ import ( func CreateNamespace(ctx context.Context, client TestClient, namespace string) error { ns := builder.ForNamespace(namespace).Result() + // Add label to avoid PSA check. + ns.Labels = map[string]string{ + "pod-security.kubernetes.io/enforce": "baseline", + "pod-security.kubernetes.io/enforce-version": "latest", + } _, err := client.ClientGo.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) if apierrors.IsAlreadyExists(err) { return nil @@ -45,6 +50,9 @@ func CreateNamespace(ctx context.Context, client TestClient, namespace string) e func CreateNamespaceWithLabel(ctx context.Context, client TestClient, namespace string, label map[string]string) error { ns := builder.ForNamespace(namespace).Result() ns.Labels = label + // Add label to avoid PSA check. + ns.Labels["pod-security.kubernetes.io/enforce"] = "baseline" + ns.Labels["pod-security.kubernetes.io/enforce-version"] = "latest" _, err := client.ClientGo.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) if apierrors.IsAlreadyExists(err) { return nil @@ -54,6 +62,11 @@ func CreateNamespaceWithLabel(ctx context.Context, client TestClient, namespace func CreateNamespaceWithAnnotation(ctx context.Context, client TestClient, namespace string, annotation map[string]string) error { ns := builder.ForNamespace(namespace).Result() + // Add label to avoid PSA check. + ns.Labels = map[string]string{ + "pod-security.kubernetes.io/enforce": "baseline", + "pod-security.kubernetes.io/enforce-version": "latest", + } ns.ObjectMeta.Annotations = annotation _, err := client.ClientGo.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) if apierrors.IsAlreadyExists(err) { diff --git a/test/e2e/util/k8s/secret.go b/test/e2e/util/k8s/secret.go index 3c3682c933..7ba6cb7aef 100644 --- a/test/e2e/util/k8s/secret.go +++ b/test/e2e/util/k8s/secret.go @@ -5,7 +5,7 @@ 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 +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, @@ -22,7 +22,6 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" - "golang.org/x/net/context" v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -73,25 +72,3 @@ func WaitForSecretsComplete(c clientset.Interface, ns, secretName string) error func GetSecret(c clientset.Interface, ns, secretName string) (*v1.Secret, error) { return c.CoreV1().Secrets(ns).Get(context.TODO(), secretName, metav1.GetOptions{}) } - -//CreateVCCredentialSecret refer to https://github.com/vmware-tanzu/velero-plugin-for-vsphere/blob/v1.3.0/docs/vanilla.md -func CreateVCCredentialSecret(c clientset.Interface, veleroNamespace string) error { - secret, err := GetSecret(c, "kube-system", "vsphere-config-secret") - if err != nil { - return err - } - vsphereCfg, exist := secret.Data["csi-vsphere.conf"] - if !exist { - return errors.New("failed to retrieve csi-vsphere config") - } - se := &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "velero-vsphere-config-secret", - Namespace: veleroNamespace, - }, - Type: v1.SecretTypeOpaque, - Data: map[string][]byte{"csi-vsphere.conf": vsphereCfg}, - } - _, err = c.CoreV1().Secrets(veleroNamespace).Create(context.TODO(), se, metav1.CreateOptions{}) - return err -} diff --git a/test/e2e/util/kibishii/kibishii_utils.go b/test/e2e/util/kibishii/kibishii_utils.go index c09ac7748c..2306d51f69 100644 --- a/test/e2e/util/kibishii/kibishii_utils.go +++ b/test/e2e/util/kibishii/kibishii_utils.go @@ -156,6 +156,13 @@ func installKibishii(ctx context.Context, namespace string, cloudPlatform, veler return errors.Wrapf(err, "failed to install kibishii, stderr=%s", stderr) } + labelNamespaceCmd := exec.CommandContext(ctx, "kubectl", "label", "namespace", namespace, "pod-security.kubernetes.io/enforce=baseline", "pod-security.kubernetes.io/enforce-version=latest", "--overwrite=true") + _, stderr, err = veleroexec.RunCommand(labelNamespaceCmd) + fmt.Printf("Label namespace with PSA policy: %s\n", labelNamespaceCmd) + if err != nil { + return errors.Wrapf(err, "failed to label namespace with PSA policy, stderr=%s", stderr) + } + kibishiiSetWaitCmd := exec.CommandContext(ctx, "kubectl", "rollout", "status", "statefulset.apps/kibishii-deployment", "-n", namespace, "-w", "--timeout=30m") _, stderr, err = veleroexec.RunCommand(kibishiiSetWaitCmd) @@ -174,7 +181,7 @@ func installKibishii(ctx context.Context, namespace string, cloudPlatform, veler } func generateData(ctx context.Context, namespace string, kibishiiData *KibishiiData) error { - timeout, _ := context.WithTimeout(context.Background(), time.Minute*10) + timeout, _ := context.WithTimeout(context.Background(), time.Minute*20) kibishiiGenerateCmd := exec.CommandContext(timeout, "kubectl", "exec", "-n", namespace, "jump-pad", "--", "/usr/local/bin/generate.sh", strconv.Itoa(kibishiiData.Levels), strconv.Itoa(kibishiiData.DirsPerLevel), strconv.Itoa(kibishiiData.FilesPerLevel), strconv.Itoa(kibishiiData.FileLength), diff --git a/test/e2e/util/velero/install.go b/test/e2e/util/velero/install.go index 2c8938976f..bdf2036cf5 100644 --- a/test/e2e/util/velero/install.go +++ b/test/e2e/util/velero/install.go @@ -5,7 +5,7 @@ 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 +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, @@ -42,6 +42,11 @@ import ( . "github.com/vmware-tanzu/velero/test/e2e/util/k8s" ) +const ( + KubeSystemNamespace = "kube-system" + VSphereCSIControllerNamespace = "vmware-system-csi" +) + // we provide more install options other than the standard install.InstallOptions in E2E test type installOptions struct { *install.InstallOptions @@ -110,7 +115,7 @@ func VeleroInstall(ctx context.Context, veleroCfg *VeleroConfig, useVolumeSnapsh return nil } -//configvSpherePlugin refers to https://github.com/vmware-tanzu/velero-plugin-for-vsphere/blob/v1.3.0/docs/vanilla.md +// configvSpherePlugin refers to https://github.com/vmware-tanzu/velero-plugin-for-vsphere/blob/v1.3.0/docs/vanilla.md func configvSpherePlugin(cli TestClient) error { var err error vsphereSecret := "velero-vsphere-config-secret" @@ -121,7 +126,7 @@ func configvSpherePlugin(cli TestClient) error { if err := CreateNamespace(context.Background(), cli, VeleroCfg.VeleroNamespace); err != nil { return errors.WithMessagef(err, "Failed to create Velero %s namespace", VeleroCfg.VeleroNamespace) } - if err := CreateVCCredentialSecret(cli.ClientGo, VeleroCfg.VeleroNamespace); err != nil { + if err := createVCCredentialSecret(cli.ClientGo, VeleroCfg.VeleroNamespace); err != nil { return errors.WithMessagef(err, "Failed to create virtual center credential secret in %s namespace", VeleroCfg.VeleroNamespace) } if err := WaitForSecretsComplete(cli.ClientGo, VeleroCfg.VeleroNamespace, vsphereSecret); err != nil { @@ -427,3 +432,37 @@ func VeleroUninstall(ctx context.Context, cli, namespace string) error { fmt.Println("Velero uninstalled ⛵") return nil } + +// createVCCredentialSecret refer to https://github.com/vmware-tanzu/velero-plugin-for-vsphere/blob/v1.3.0/docs/vanilla.md +func createVCCredentialSecret(c clientset.Interface, veleroNamespace string) error { + secret, err := getVCCredentialSecret(c) + if err != nil { + return err + } + vsphereCfg, exist := secret.Data["csi-vsphere.conf"] + if !exist { + return errors.New("failed to retrieve csi-vsphere config") + } + se := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "velero-vsphere-config-secret", + Namespace: veleroNamespace, + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{"csi-vsphere.conf": vsphereCfg}, + } + _, err = c.CoreV1().Secrets(veleroNamespace).Create(context.TODO(), se, metav1.CreateOptions{}) + return err +} + +// Reference https://github.com/vmware-tanzu/velero-plugin-for-vsphere/blob/main/docs/vanilla.md#create-vc-credential-secret +// Read secret from kube-system namespace first, if not found, try with vmware-system-csi. +func getVCCredentialSecret(c clientset.Interface) (secret *corev1.Secret, err error) { + secret, err = GetSecret(c, KubeSystemNamespace, "vsphere-config-secret") + if err != nil { + if apierrors.IsNotFound(err) { + secret, err = GetSecret(c, VSphereCSIControllerNamespace, "vsphere-config-secret") + } + } + return +} diff --git a/test/e2e/util/velero/velero_utils.go b/test/e2e/util/velero/velero_utils.go index 37c49feb76..bf00cb1c09 100644 --- a/test/e2e/util/velero/velero_utils.go +++ b/test/e2e/util/velero/velero_utils.go @@ -270,6 +270,24 @@ func checkSchedulePhase(ctx context.Context, veleroCLI, veleroNamespace, schedul }) } +func checkSchedulePause(ctx context.Context, veleroCLI, veleroNamespace, scheduleName string, pause bool) error { + checkCMD := exec.CommandContext(ctx, veleroCLI, "--namespace", veleroNamespace, "schedule", "get", scheduleName, "-ojson") + jsonBuf, err := CMDExecWithOutput(checkCMD) + if err != nil { + return err + } + schedule := velerov1api.Schedule{} + err = json.Unmarshal(*jsonBuf, &schedule) + if err != nil { + return err + } + + if schedule.Spec.Paused != pause { + fmt.Printf("Unexpected schedule phase got %s, expecting %s, still waiting...", schedule.Status.Phase, velerov1api.SchedulePhaseEnabled) + return nil + } + return nil +} func CheckScheduleWithResourceOrder(ctx context.Context, veleroCLI, veleroNamespace, scheduleName string, order map[string]string) error { checkCMD := exec.CommandContext(ctx, veleroCLI, "--namespace", veleroNamespace, "schedule", "get", scheduleName, "-ojson") jsonBuf, err := CMDExecWithOutput(checkCMD) @@ -442,6 +460,28 @@ func VeleroScheduleCreate(ctx context.Context, veleroCLI string, veleroNamespace return checkSchedulePhase(ctx, veleroCLI, veleroNamespace, scheduleName) } +func VeleroSchedulePause(ctx context.Context, veleroCLI string, veleroNamespace string, scheduleName string) error { + var args []string + args = append([]string{ + "--namespace", veleroNamespace, "schedule", "pause", scheduleName, + }) + if err := VeleroCmdExec(ctx, veleroCLI, args); err != nil { + return err + } + return checkSchedulePause(ctx, veleroCLI, veleroNamespace, scheduleName, true) +} + +func VeleroScheduleUnpause(ctx context.Context, veleroCLI string, veleroNamespace string, scheduleName string) error { + var args []string + args = append([]string{ + "--namespace", veleroNamespace, "schedule", "unpause", scheduleName, + }) + if err := VeleroCmdExec(ctx, veleroCLI, args); err != nil { + return err + } + return checkSchedulePause(ctx, veleroCLI, veleroNamespace, scheduleName, false) +} + func VeleroCmdExec(ctx context.Context, veleroCLI string, args []string) error { cmd := exec.CommandContext(ctx, veleroCLI, args...) cmd.Stdout = os.Stdout @@ -1168,7 +1208,7 @@ func UpdateVeleroDeployment(ctx context.Context, veleroCfg VeleroConfig) ([]stri if veleroCfg.CloudProvider == "vsphere" { args = fmt.Sprintf("s#\\\"image\\\"\\: \\\"velero\\/velero\\:v[0-9]*.[0-9]*.[0-9]\\\"#\\\"image\\\"\\: \\\"harbor-repo.vmware.com\\/velero_ci\\/velero\\:%s\\\"#g", veleroCfg.VeleroVersion) } else { - args = fmt.Sprintf("s#\\\"image\\\"\\: \\\"velero\\/velero\\:v[0-9]*.[0-9]*.[0-9]\\\"#\\\"image\\\"\\: \\\"velero\\/velero\\:%s\\\"#g", veleroCfg.VeleroVersion) + args = fmt.Sprintf("s#\\\"image\\\"\\: \\\"velero\\/velero\\:v[0-9]*.[0-9]*.[0-9]\\\"#\\\"image\\\"\\: \\\"gcr.io\\/velero-gcp\\/nightly\\/velero\\:%s\\\"#g", veleroCfg.VeleroVersion) } cmd = &common.OsCommandLine{ Cmd: "sed", @@ -1221,7 +1261,7 @@ func UpdateNodeAgent(ctx context.Context, veleroCfg VeleroConfig, dsjson string) if veleroCfg.CloudProvider == "vsphere" { args = fmt.Sprintf("s#\\\"image\\\"\\: \\\"velero\\/velero\\:v[0-9]*.[0-9]*.[0-9]\\\"#\\\"image\\\"\\: \\\"harbor-repo.vmware.com\\/velero_ci\\/velero\\:%s\\\"#g", veleroCfg.VeleroVersion) } else { - args = fmt.Sprintf("s#\\\"image\\\"\\: \\\"velero\\/velero\\:v[0-9]*.[0-9]*.[0-9]\\\"#\\\"image\\\"\\: \\\"velero\\/velero\\:%s\\\"#g", veleroCfg.VeleroVersion) + args = fmt.Sprintf("s#\\\"image\\\"\\: \\\"velero\\/velero\\:v[0-9]*.[0-9]*.[0-9]\\\"#\\\"image\\\"\\: \\\"gcr.io\\/velero-gcp\\/nightly\\/velero\\:%s\\\"#g", veleroCfg.VeleroVersion) } cmd = &common.OsCommandLine{ Cmd: "sed",