diff --git a/.github/workflows/atlas-image-build.yaml b/.github/workflows/atlas-image-build.yaml index afe01c2c3a..bb3755995a 100644 --- a/.github/workflows/atlas-image-build.yaml +++ b/.github/workflows/atlas-image-build.yaml @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 0 - - uses: tj-actions/changed-files@d6babd6899969df1a11d14c368283ea4436bca78 # v44.5.2 + - uses: tj-actions/changed-files@cc733854b1f224978ef800d29e4709d5ee2883e4 # v44.5.5 name: Get changed files id: changed-files with: @@ -57,7 +57,7 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@94f8f8c2eec4bc3f1d78c1755580779804cb87b2 # v6.0.1 + uses: docker/build-push-action@31159d49c0d4756269a0940a750801a1ea5d7003 # v6.1.0 with: context: "{{defaultContext}}:docker/mongodb-atlas" push: true diff --git a/.github/workflows/example-images-scanning.yaml b/.github/workflows/example-images-scanning.yaml new file mode 100644 index 0000000000..ecc3957b75 --- /dev/null +++ b/.github/workflows/example-images-scanning.yaml @@ -0,0 +1,17 @@ +name: Example images scanning +permissions: + contents: read +on: + workflow_dispatch: + workflow_run: + workflows: ["Build and test"] + types: + - completed + branches: + - master + +jobs: + scan-images: + uses: ./.github/workflows/images-vulnerability-scanning.yaml + with: + images_file: "build/example_images.json" diff --git a/.github/workflows/grype-vulnerability-scanner.yaml b/.github/workflows/grype-vulnerability-scanner.yaml deleted file mode 100644 index 46f7192261..0000000000 --- a/.github/workflows/grype-vulnerability-scanner.yaml +++ /dev/null @@ -1,70 +0,0 @@ -name: container vulnerability scanning -permissions: - contents: read -on: - workflow_dispatch: - workflow_run: - workflows: ["Build and test"] - types: - - completed - branches: - - master - -jobs: - vulnerability-scanner: - runs-on: ubuntu-20.04 - steps: - - name: Create repo directory before checking out latest code - run: mkdir -p repo - - name: Checkout the latest code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - with: - ref: master - path: repo - - name: Read JSON file - id: valid-image-json - run: | - EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) - echo "images_list<<$EOF" >> $GITHUB_OUTPUT - cat repo/build/valid_images.json >> $GITHUB_OUTPUT - echo "$EOF" >> $GITHUB_OUTPUT - - name: Reading output variable - run: echo ${{fromJson(steps.valid-image-json.outputs.images_list)}} - outputs: - valid_images: ${{steps.valid-image-json.outputs.images_list}} - report-analysis: - runs-on: ubuntu-20.04 - needs: - - vulnerability-scanner - strategy: - max-parallel: 3 - fail-fast: false - matrix: - images: ${{fromJson(needs.vulnerability-scanner.outputs.valid_images).images}} - steps: - - name: Printing Image Registry - id: image-registry - run: echo "image_registry=${{fromJson(needs.vulnerability-scanner.outputs.valid_images).image_registry}}" >> "$GITHUB_ENV" - - name: Printing Image Tag - id: image-tag - run: echo "image_tag=${{fromJson(needs.vulnerability-scanner.outputs.valid_images).tag}}" >> "$GITHUB_ENV" - - name: Printing Image Path - run: echo "image_path=${{env.image_registry}}/${{matrix.images}}:${{env.image_tag}}" >> "$GITHUB_ENV" - - name: Running vulnerability scanner - uses: anchore/scan-action@v3 - id: vulnerability-scanning - with: - image: ${{env.image_path}} - fail-build: false - output-format: json - only-fixed: true - - name: Create repo directory before checking out latest code - run: mkdir -p repo - - name: Checkout the latest code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - with: - ref: master - path: repo - - name: Parsing vulnerability scanner report - run: go run repo/pkg/tools/grype_report_parser_tool.go -s "High,Critical" -p results.json --github - diff --git a/.github/workflows/images-vulnerability-scanning.yaml b/.github/workflows/images-vulnerability-scanning.yaml new file mode 100644 index 0000000000..5f5ea50544 --- /dev/null +++ b/.github/workflows/images-vulnerability-scanning.yaml @@ -0,0 +1,59 @@ +name: Images vulnerability scanning +permissions: + contents: read +on: + workflow_call: + inputs: + images_file: + required: true + type: string + +jobs: + discover-images: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: Read JSON file + id: images-json + ## Select images file and print it to the output var + run: | + EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) + echo "images_json<<$EOF" >> $GITHUB_OUTPUT + cat ${{ inputs.images_file }} >> $GITHUB_OUTPUT + echo "$EOF" >> $GITHUB_OUTPUT + - name: Showing output variable + run: echo ${{fromJson(steps.images-json.outputs.images_json)}} + outputs: + images-json: ${{steps.images-json.outputs.images_json}} + report-analysis: + runs-on: ubuntu-latest + needs: + - discover-images + strategy: + max-parallel: 3 + fail-fast: false + matrix: + images: ${{fromJson(needs.discover-images.outputs.images-json).images}} + name: ${{ matrix.images }} + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: Printing Image Registry + id: image-registry + run: echo "image_registry=${{fromJson(needs.discover-images.outputs.images-json).image_registry}}" >> "$GITHUB_ENV" + - name: Printing Image Tag + id: image-tag + run: echo "image_tag=${{fromJson(needs.discover-images.outputs.images-json).tag}}" >> "$GITHUB_ENV" + - name: Printing Image Path + run: echo "image_path=${{env.image_registry}}/${{matrix.images}}:${{env.image_tag}}" >> "$GITHUB_ENV" + - name: Running vulnerability scanner + uses: anchore/scan-action@v3 + id: vulnerability-scanning + with: + image: ${{env.image_path}} + fail-build: false + output-format: json + only-fixed: true + severity-cutoff: medium + - name: Parsing vulnerability scanner report + run: go run pkg/tools/grype_report_parser_tool.go -s "Medium,High,Critical" -p results.json --github + diff --git a/.github/workflows/kanister-image-build.yaml b/.github/workflows/kanister-image-build.yaml index 7306d56026..e2cd940570 100644 --- a/.github/workflows/kanister-image-build.yaml +++ b/.github/workflows/kanister-image-build.yaml @@ -71,7 +71,7 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@94f8f8c2eec4bc3f1d78c1755580779804cb87b2 # v6.0.1 + uses: docker/build-push-action@31159d49c0d4756269a0940a750801a1ea5d7003 # v6.1.0 with: context: "{{defaultContext}}:docker/build" platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index f9ddddd9ca..e6bb83fb85 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -44,6 +44,22 @@ jobs: - name: restore_gosum run: echo "${{needs.gomod.outputs.gosum}}" > go.sum - run: make golint + + reno_lint: + runs-on: ubuntu-20.04 + needs: gomod + steps: + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + with: + fetch-depth: 0 + - name: reset_git_extension + run: git config --unset-all extensions.worktreeconfig + - name: reno_lint + run: make reno-lint + ## Reno lint does not catch some errors which make reno report fail + - name: reno_report_check + run: make reno-report + test: runs-on: ubuntu-20.04 needs: gomod diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml new file mode 100644 index 0000000000..40c509cd2e --- /dev/null +++ b/.github/workflows/pre-release.yml @@ -0,0 +1,57 @@ +name: Pre release + +on: + workflow_dispatch: + inputs: + release_tag: + description: 'Image tag in the format x.x.x' + required: true + type: string + +env: + RELEASE_TAG: ${{ inputs.release_tag }} + PRERELEASE_DOCS_BRANCH: 'dg8d45z' + +jobs: + ## TODO we can add a condition like github.actor.role == 'Maintainer' to limit trigger to maintainers only + create_pr: + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: write + steps: + - name: checkout_repo + uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 # necessary for CURRENT_TAG tracing + - name: fetch_tags + run: git fetch --tags origin + - name: bump_version + run: | + export CURRENT_TAG=$(git describe --abbrev=0 --tags) + echo ./build/bump_version.sh "${CURRENT_TAG}" "${RELEASE_TAG}" + ./build/bump_version.sh "${CURRENT_TAG}" "${RELEASE_TAG}" + make reno-report VERSION="${RELEASE_TAG}" + - name: commit_changes + run: | + git config --global user.name 'Kasten Production' + git config --global user.email 'infra@kasten.io' + git checkout -B "kan-docs-${PRERELEASE_DOCS_BRANCH}-${RELEASE_TAG}" + git add -A + git commit -s -m "pre-release: Update version to ${RELEASE_TAG}" + - name: push_changes + run: git push origin "kan-docs-${PRERELEASE_DOCS_BRANCH}-${RELEASE_TAG}" + - name: create_pr_body + run: | + echo "Update version to ${RELEASE_TAG}" > PR_BODY_FILE + echo "" >> PR_BODY_FILE + echo "Please check the changelog for the following merges:" >> PR_BODY_FILE + export CURRENT_TAG=$(git describe --abbrev=0 --tags) + git log ${CURRENT_TAG}..kan-docs-${PRERELEASE_DOCS_BRANCH}-${RELEASE_TAG} --pretty="- %h: %s" | grep -v ': test' | grep -v ': doc' | grep -v ': build' | grep -v ': deps' >> PR_BODY_FILE + - name: create_pr + run: | + gh pr create --title "pre-release: Update version to ${RELEASE_TAG}" -F PR_BODY_FILE --head "kan-docs-${PRERELEASE_DOCS_BRANCH}-${RELEASE_TAG}" --base master --reviewer pavannd1,viveksinghggits,hairyhum --label kueue + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + diff --git a/.github/workflows/published-images-scanning.yaml b/.github/workflows/published-images-scanning.yaml new file mode 100644 index 0000000000..1ddd5dd10b --- /dev/null +++ b/.github/workflows/published-images-scanning.yaml @@ -0,0 +1,17 @@ +name: Published images scanning +permissions: + contents: read +on: + workflow_dispatch: + workflow_run: + workflows: ["Build and test"] + types: + - completed + branches: + - master + +jobs: + scan-images: + uses: ./.github/workflows/images-vulnerability-scanning.yaml + with: + images_file: "build/published_images.json" diff --git a/.gitignore b/.gitignore index 384cc3e88d..14bb7c3793 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ /dist **/*.swp /.idea +/releasenotes/config.yaml +CHANGELOG.rst +CHANGELOG_CURRENT.rst diff --git a/.goreleaser.yml b/.goreleaser.yml index 45b4ce2b3a..c9b1d4c37b 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -153,5 +153,6 @@ changelog: exclude: - '^docs:' - '^test:' + - '^pre-release:' archives: - allow_different_binary_count: true diff --git a/CODE_REVIEW.md b/CODE_REVIEW.md new file mode 100644 index 0000000000..7979311586 --- /dev/null +++ b/CODE_REVIEW.md @@ -0,0 +1,74 @@ +# Code review requiements + +This document describes responsibilities of code reviewers when reviewing PRs. + +Pull request process is described in [contributing guide](./CONTRIBUTING.md#submitting-pull-requests) + +## Base checklist + +- All automated test steps pass (e.g. tests, lints, build) +- PR title follows [commit conventions](CONTRIBUTING.md#commit-conventions) + - If PR format is different, reviewer should change it to follow the conventions +- PR has a description with reasoning and change overview + - If description is missing but clear for reviewer, reviewer may request the author to add the description or edit description by themselves +- New feature or fix has tests proving it works + - Reviewer should request changes from contributor to add tests +- If change in the PR needs documentation + - Reviewer should request new docs or update to existing docs + - `/docs` and `/docs_new` need to be kept in sync until we deprecate `/docs` +- If PR introduces breaking changes, fixes a bug or adds a new feature, there should be a [release note](#release-notes) + - Reviewer may request changes from the contributor to add a release note + - Reviewer may add a release note by themself in order to unblock the merge process + +## Requesting changes + +It's recommended to request changes by submitting `comment` type reviews. +`Request changes` type review would block merging until requester approves the +changes, this can slow down the process if there are multiple reviewers. + +## Approving and merging + +We use `kueue` bot to merge approved PRs. + +If PR is approved, all checks are passing and it has the `kueue` label, it will +be automatically squashed and merged. + +For PRs from Kanister developers, the author should add the `kueue` label after +PR was approved. + +For PRs from community members, the reviewer should add the `kueue` label. + +## Release notes + +Kanister is using the [reno](https://docs.openstack.org/reno/latest/) tool to generate changelogs from release note files. + +To add release note one could run: + +``` +make reno-new note= +``` + +Note name should be a short description of a change. + +File format is described in [reno docs](https://docs.openstack.org/reno/latest/user/usage.html#editing-a-release-note) + +Typical examples would be: + +``` +--- +features: + - Added new functionality doing X +``` + +Or: + +``` +--- +fixes: + - Fixed bug with pod output format +upgrade: + - Make sure custom blueprints follow pod output format spec +``` + +See [release notes](./releasenotes/README.md) for more info. + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 80e7b321d2..67f8e6bce7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -95,6 +95,16 @@ We are using squash and merge approach to PRs which means that commit descriptio It's recommended to use conventional commits when strarting a PR, but follow-up commits in the PR don't have to follow the convention. +### Release notes + +If submitted change fixes a bug, introduces a new feature or breaking change, contributor should add a release note. +Kanister is using the [reno](https://docs.openstack.org/reno/latest/) tool to track release notes. + +Release note can be added with `make reno-new note=` command, which will create a note file. +Contributor should edit and commit the note file. + +See [release notes](./releasenotes/README.md) for more info. + ### Submitting Pull Requests **PR titles should be in following format:** diff --git a/Makefile b/Makefile index 5c732bb09a..22fdb41de5 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ REGISTRY ?= kanisterio ARCH ?= amd64 # This version-strategy uses git tags to set the version string -VERSION := $(shell git describe --tags --always --dirty) +VERSION ?= $(shell git describe --tags --always --dirty) # # This version-strategy uses a manual value to set the version string #VERSION := 1.2.3 @@ -70,6 +70,10 @@ GOBORING ?= "" CONTROLLER_TOOLS_VERSION ?= "v0.12.0" +## Changelog file for goreleaser + +CHANGELOG_FILE ?= ./CHANGELOG_CURRENT.md + # If you want to build all binaries, see the 'all-build' rule. # If you want to build all containers, see the 'all-container' rule. # If you want to build AND push all containers, see the 'all-push' rule. @@ -207,7 +211,7 @@ ifeq ($(DOCKER_BUILD),"true") @echo "running CMD in the containerized build environment" @PWD=$(PWD) ARCH=$(ARCH) PKG=$(PKG) GITHUB_TOKEN=$(GITHUB_TOKEN) CMD="$(CMD)" /bin/bash ./build/run_container.sh run else - @/bin/bash $(CMD) + @/bin/bash -c "$(CMD)" endif clean: dotfile-clean bin-clean @@ -236,7 +240,7 @@ release-helm: @/bin/bash ./build/release_helm.sh $(VERSION) gorelease: - @$(MAKE) run CMD="./build/gorelease.sh" + @$(MAKE) run CMD="CHANGELOG_FILE=$(CHANGELOG_FILE) ./build/gorelease.sh" release-snapshot: @$(MAKE) run CMD="GORELEASER_CURRENT_TAG=v9.99.9-dev goreleaser --debug release --rm-dist --snapshot --timeout=60m0s" @@ -284,3 +288,11 @@ uninstall-crds: ## Uninstall CRDs from the K8s cluster specified in ~/.kube/conf manifests: ## Generates CustomResourceDefinition objects. @$(MAKE) run CMD="./build/generate_crds.sh ${CONTROLLER_TOOLS_VERSION}" +reno-new: + @PWD=$(PWD) ARCH=$(ARCH) PKG=$(PKG) GITHUB_TOKEN=$(GITHUB_TOKEN) CMD="EDITOR=vim reno new $(note) --edit" /bin/bash ./build/run_container.sh shell + +reno-report: + @$(MAKE) run CMD="./build/reno_report.sh $(VERSION)" + +reno-lint: + @$(MAKE) run CMD="reno lint" diff --git a/build/example_images.json b/build/example_images.json new file mode 100644 index 0000000000..1402cd0dc9 --- /dev/null +++ b/build/example_images.json @@ -0,0 +1,15 @@ +{ + "image_registry": "ghcr.io/kanisterio", + "images": [ + "mysql-sidecar", + "kafka-adobe-s3-sink-connector", + "postgres-kanister-tools", + "postgresql", + "cassandra", + "mongodb", + "es-sidecar", + "kafka-adobe-s3-source-connector", + "mssql-tools" + ], + "tag": "v9.99.9-dev" +} diff --git a/build/gorelease.sh b/build/gorelease.sh index 9634524170..52eeca322a 100755 --- a/build/gorelease.sh +++ b/build/gorelease.sh @@ -25,4 +25,14 @@ then echo "You can generate a token here: https://github.com/settings/tokens/new" exit 1 fi -goreleaser release --parallelism=1 --rm-dist --debug --timeout 120m + +## Set default changelog file +CHANGELOG_FILE=${CHANGELOG_FILE:-./CHANGELOG_CURRENT.md} + +RELEASE_NOTES="" +if [ -n "${CHANGELOG_FILE:-}" ] +then + RELEASE_NOTES="--release-notes ${CHANGELOG_FILE}" +fi + +goreleaser release --parallelism=1 --rm-dist --debug --timeout 120m ${RELEASE_NOTES} diff --git a/build/published_images.json b/build/published_images.json new file mode 100644 index 0000000000..b2a7db408b --- /dev/null +++ b/build/published_images.json @@ -0,0 +1,10 @@ +{ + "image_registry": "ghcr.io/kanisterio", + "images": [ + "kanister-kubectl-1.18", + "controller", + "kanister-tools", + "repo-server-controller" + ], + "tag": "v9.99.9-dev" +} diff --git a/build/push_images.sh b/build/push_images.sh index a6ab15dadb..7d7b749e8a 100755 --- a/build/push_images.sh +++ b/build/push_images.sh @@ -19,19 +19,28 @@ set -o nounset IMAGE_REGISTRY="ghcr.io/kanisterio" -IMAGES_NAME_PATH="build/valid_images.json" - -IMAGES=(`cat ${IMAGES_NAME_PATH} | jq -r .images[]`) +PUBLISHED_IMAGES_NAME_PATH="build/published_images.json" +EXAMPLE_IMAGES_NAME_PATH="build/example_images.json" TAG=${1:-"v9.99.9-dev"} COMMIT_SHA_TAG=commit-${COMMIT_SHA:?"COMMIT_SHA is required"} SHORT_COMMIT_SHA_TAG=short-commit-${COMMIT_SHA::12} -for i in ${IMAGES[@]}; do - docker tag $IMAGE_REGISTRY/$i:$TAG $IMAGE_REGISTRY/$i:$COMMIT_SHA_TAG - docker tag $IMAGE_REGISTRY/$i:$TAG $IMAGE_REGISTRY/$i:$SHORT_COMMIT_SHA_TAG - docker push $IMAGE_REGISTRY/$i:$TAG - docker push $IMAGE_REGISTRY/$i:$COMMIT_SHA_TAG - docker push $IMAGE_REGISTRY/$i:$SHORT_COMMIT_SHA_TAG -done +push_images() { + images_file_path=$1 + + images=$(jq -r .images[] "${images_file_path}") + + for i in ${images[@]}; do + docker tag $IMAGE_REGISTRY/$i:$TAG $IMAGE_REGISTRY/$i:$COMMIT_SHA_TAG + docker tag $IMAGE_REGISTRY/$i:$TAG $IMAGE_REGISTRY/$i:$SHORT_COMMIT_SHA_TAG + docker push $IMAGE_REGISTRY/$i:$TAG + docker push $IMAGE_REGISTRY/$i:$COMMIT_SHA_TAG + docker push $IMAGE_REGISTRY/$i:$SHORT_COMMIT_SHA_TAG + done +} + +push_images $PUBLISHED_IMAGES_NAME_PATH + +push_images $EXAMPLE_IMAGES_NAME_PATH diff --git a/build/reno_report.sh b/build/reno_report.sh new file mode 100755 index 0000000000..a3303bce29 --- /dev/null +++ b/build/reno_report.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +version=${1:-"''"} + +## Generate changelog files using reno + +## Used tools: +## - reno for changelog generation +## - rst2md for report conversion from rst to markdown + +## This script generates changelog files in markdown format. +## Generated files are: +## - CHANGELOG.md contains all versions history (for versions which have reno notes) +## - CHANGELOG_CURRENT.md contains only current version changes + +## Arguments: +## version ($1) - string to use for "current" version number + +## Latest notes should go to current version +## We generate report before tagging the repo, so we need to set this version here +# echo "unreleased_version_title: ${version}" > reno.yaml +sed "s/unreleased_version_title: ''/unreleased_version_title: ${version}/g" reno.yaml > releasenotes/config.yaml + +# Update changelog for all versions: + +## Generate rst report +echo reno report --output ./CHANGELOG.rst +reno report --output ./CHANGELOG.rst + +## Convert rst to markdown +rst2md ./CHANGELOG.rst --output ./CHANGELOG.md + +# Generate changelog for current version only: + +## Reno `--version` flag does not support "unreleased" setting and requires specific version, even if it's dynamic +## To generate dynamic version, use `reno list`. +## It will be replaced by `unreleased_version_title` setting in the actual report file +UNRELEASED_VERSION=$(reno list 2>/dev/null | grep -E "^[0-9]+\.[0-9]+\.[0-9]+\-[0-9]+") + +if [ -n "${UNRELEASED_VERSION}" ] +then + ## Generate rst report + echo reno report --version=${UNRELEASED_VERSION} --output ./CHANGELOG_CURRENT.rst + reno report --version=${UNRELEASED_VERSION} --output ./CHANGELOG_CURRENT.rst + ## Convert rst to markdown + rst2md ./CHANGELOG_CURRENT.rst --output ./CHANGELOG_CURRENT.md +fi diff --git a/build/run_container.sh b/build/run_container.sh index 68ca2fa86e..e2f4e5d643 100755 --- a/build/run_container.sh +++ b/build/run_container.sh @@ -23,7 +23,7 @@ set -o nounset PWD="${PWD:-$(pwd)}" DOCS_BUILD_IMAGE="${DOCS_BUILD_IMAGE:-ghcr.io/kanisterio/docker-sphinx:0.2.0}" -BUILD_IMAGE="${BUILD_IMAGE:-ghcr.io/kanisterio/build:v0.0.31}" +BUILD_IMAGE="${BUILD_IMAGE:-ghcr.io/kanisterio/build:latest}" PKG="${PKG:-github.com/kanisterio/kanister}" ARCH="${ARCH:-amd64}" @@ -64,6 +64,7 @@ run_build_container() { --rm \ --net host \ -e GITHUB_TOKEN="${github_token}" \ + -e DOCKER_BUILD="false" \ ${minikube_dir_binding} \ -v "${HOME}/.kube:/root/.kube" \ -v "${PWD}/.go/pkg:/go/pkg" \ diff --git a/build/valid_images.json b/build/valid_images.json deleted file mode 100644 index 5d94c5fb97..0000000000 --- a/build/valid_images.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "image_registry": "ghcr.io/kanisterio", - "images": [ "mysql-sidecar", - "kafka-adobe-s3-sink-connector", - "postgres-kanister-tools", - "postgresql", - "cassandra", - "kanister-kubectl-1.18", - "mongodb", - "es-sidecar", - "controller", - "kanister-tools", - "kafka-adobe-s3-source-connector", - "mssql-tools"], - "tag": "v9.99.9-dev" -} diff --git a/docker/build/Dockerfile b/docker/build/Dockerfile index 63a8c80daf..e1cf992f75 100644 --- a/docker/build/Dockerfile +++ b/docker/build/Dockerfile @@ -29,6 +29,12 @@ RUN git config --global --add safe.directory /go/src/github.com/kanisterio/kanis # Adding CRD documentation generation tool. RUN GOBIN=/usr/local/bin go install github.com/ahmetb/gen-crd-api-reference-docs@v0.3.0 +RUN apt-get update && apt-get install -y pip + +RUN pip install reno nb2plots + +RUN apt-get install -y vim + ENV CGO_ENABLED=0 \ GO111MODULE="on" \ GOROOT="/usr/local/go" \ diff --git a/go.mod b/go.mod index 3f136d8e40..6903a5a4a4 100644 --- a/go.mod +++ b/go.mod @@ -12,16 +12,18 @@ replace ( gopkg.in/check.v1 => github.com/kastenhq/check v0.0.0-20180626002341-0264cfcea734 ) -// Direct and indirect dependencies are in separate require sections +// Direct and indirect dependencies are in separate require sections. +// Keep *all* direct dependencies in this section; and all indirect +// dependencies in the require block following this one. require ( github.com/Azure/azure-sdk-for-go v68.0.0+incompatible github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.1 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.3.0 github.com/Masterminds/semver v1.5.0 github.com/Masterminds/sprig v2.22.0+incompatible - github.com/aws/aws-sdk-go v1.54.3 + github.com/aws/aws-sdk-go v1.54.7 github.com/dustin/go-humanize v1.0.1 github.com/go-logr/logr v1.4.2 github.com/go-openapi/strfmt v0.23.0 @@ -32,6 +34,8 @@ require ( github.com/hashicorp/go-version v1.7.0 github.com/jpillora/backoff v1.0.0 github.com/json-iterator/go v1.1.12 + github.com/kanisterio/errkit v0.0.1 + github.com/kanisterio/safecli v0.0.8 github.com/kopia/kopia v0.17.1-0.20240514043650-951f126b3c5e github.com/kubernetes-csi/external-snapshotter/client/v4 v4.2.0 github.com/lib/pq v1.10.9 @@ -48,7 +52,7 @@ require ( go.uber.org/zap v1.27.0 golang.org/x/oauth2 v0.21.0 gonum.org/v1/gonum v0.15.0 - google.golang.org/api v0.184.0 + google.golang.org/api v0.185.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 @@ -67,7 +71,9 @@ require ( ) require ( - cloud.google.com/go v0.114.0 // indirect + cloud.google.com/go v0.115.0 // indirect + cloud.google.com/go/auth v0.5.1 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect cloud.google.com/go/compute/metadata v0.3.0 // indirect cloud.google.com/go/iam v1.1.8 // indirect cloud.google.com/go/storage v1.41.0 // indirect @@ -75,6 +81,9 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 // indirect; indirect; github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.11.27 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect + github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect @@ -86,9 +95,11 @@ require ( github.com/alessio/shellescape v1.4.2 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/chmduquesne/rollinghash v4.0.0+incompatible // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/danieljoos/wincred v1.2.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/edsrzf/mmap-go v1.1.0 // indirect @@ -98,23 +109,28 @@ require ( github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/fatih/color v1.17.0 // indirect github.com/felixge/fgprof v0.9.4 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/foomo/htpasswd v0.0.0-20200116085101-e3a90e78da9c // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.2.4 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-openapi/errors v0.22.0 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.3 // indirect + github.com/goccy/go-json v0.10.2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang/glog v1.2.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.0.1 // indirect + github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f // indirect @@ -124,8 +140,11 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.4 // indirect github.com/gorilla/mux v1.8.1 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/hanwen/go-fuse/v2 v2.5.1 // indirect + github.com/hashicorp/cronexpr v1.1.2 // indirect github.com/huandu/xstrings v1.2.0 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -153,6 +172,8 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/mxk/go-vss v1.2.0 // indirect github.com/natefinch/atomic v1.0.1 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect @@ -175,9 +196,15 @@ require ( github.com/zeebo/blake3 v0.2.3 // indirect go.mongodb.org/mongo-driver v1.14.0 // indirect go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel v1.26.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 // indirect + go.opentelemetry.io/otel/metric v1.26.0 // indirect go.opentelemetry.io/otel/sdk v1.26.0 // indirect go.opentelemetry.io/otel/trace v1.26.0 // indirect + go.opentelemetry.io/proto/otlp v1.2.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.24.0 // indirect @@ -191,11 +218,11 @@ require ( golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto v0.0.0-20240604185151-ef581f913117 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect + google.golang.org/genproto v0.0.0-20240617180043-68d350f18fd4 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 // indirect google.golang.org/grpc v1.64.0 // indirect - google.golang.org/protobuf v1.34.1 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/kothar/go-backblaze.v0 v0.0.0-20210124194846-35409b867216 // indirect @@ -210,31 +237,3 @@ require ( sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) - -require github.com/kanisterio/safecli v0.0.8 - -require ( - cloud.google.com/go/auth v0.5.1 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect - github.com/Azure/go-autorest/autorest v0.11.27 // indirect - github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect - github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-ole/go-ole v1.3.0 // indirect - github.com/goccy/go-json v0.10.2 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect - github.com/google/gnostic-models v0.6.8 // indirect - github.com/gorilla/websocket v1.5.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect - github.com/hashicorp/cronexpr v1.1.2 // indirect - github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/mxk/go-vss v1.2.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 // indirect - go.opentelemetry.io/otel/metric v1.26.0 // indirect - go.opentelemetry.io/proto/otlp v1.2.0 // indirect -) diff --git a/go.sum b/go.sum index c88266d654..278bc47f69 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxK cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= -cloud.google.com/go v0.114.0 h1:OIPFAdfrFDFO2ve2U7r/H5SwSbBzEdrBdE7xkgwc+kY= -cloud.google.com/go v0.114.0/go.mod h1:ZV9La5YYxctro1HTPug5lXH/GefROyW8PPD4T8n9J8E= +cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= +cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= cloud.google.com/go/auth v0.5.1 h1:0QNO7VThG54LUzKiQxv8C6x1YX7lUrzlAa1nVLF8CIw= cloud.google.com/go/auth v0.5.1/go.mod h1:vbZT8GjzDf3AVqCcQmqeeM32U9HBFc32vVVAbwDsa6s= cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= @@ -27,8 +27,8 @@ github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 h1:1nGuui+4POelzDwI7RG56yfQJHCnKvwfMoU7VsEp+Zg= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0/go.mod h1:99EvauvlcJ1U06amZiksfYz/3aFGyIhWGHVyiZXtBAI= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 h1:H+U3Gk9zY56G3u872L82bk4thcsy2Gghb9ExT4Zvm1o= github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0/go.mod h1:mgrmMSgaLp9hmax62XQTd0N4aAqSE5E0DulSpVYK7vc= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.1 h1:UPeCRD+XY7QlaGQte2EVI2iOcWvUYA2XY8w5T/8v0NQ= @@ -107,8 +107,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.54.3 h1:Bk+EXoq6v5I1xmHR9GQGpsMWZZFXs+FD+5uPyEmfgX0= -github.com/aws/aws-sdk-go v1.54.3/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go v1.54.7 h1:k1wJ+NMOsXgq/Lsa0y1mS0DFoDeHFPcz2OjCq5H5Mjg= +github.com/aws/aws-sdk-go v1.54.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -179,8 +179,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/foomo/htpasswd v0.0.0-20200116085101-e3a90e78da9c h1:DBGU7zCwrrPPDsD6+gqKG8UfMxenWg9BOJE/Nmfph+4= github.com/foomo/htpasswd v0.0.0-20200116085101-e3a90e78da9c/go.mod h1:SHawtolbB0ZOFoRWgDwakX5WpwuIWAK88bUXVZqK0Ss= -github.com/frankban/quicktest v1.13.1 h1:xVm/f9seEhZFL9+n5kv5XLrGwy6elc4V9v/XFY2vmd8= -github.com/frankban/quicktest v1.13.1/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= @@ -365,6 +365,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kanisterio/errkit v0.0.1 h1:oT8n9IknGcfE5xlc2stczMTKrDWEAifC/laAk8rBDpQ= +github.com/kanisterio/errkit v0.0.1/go.mod h1:ViQ6kPJ2gTJDEvRytmwde7pzG9/sndObF9BPZoEZixc= github.com/kanisterio/safecli v0.0.8 h1:flvTiGksy/a0+zvqjaBSJwxESu/nFcG65yttmR0XwiA= github.com/kanisterio/safecli v0.0.8/go.mod h1:KBraqj8mdv2cwAr9wecknGUb8jztTzUik0r7uE6yRA8= github.com/kastenhq/check v0.0.0-20180626002341-0264cfcea734 h1:qulsCaCv+O2y9/sQ9nd5KChnAgFOWakTHQ9ZADjs6DQ= @@ -751,8 +753,8 @@ google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.184.0 h1:dmEdk6ZkJNXy1JcDhn/ou0ZUq7n9zropG2/tR4z+RDg= -google.golang.org/api v0.184.0/go.mod h1:CeDTtUEiYENAf8PPG5VZW2yNp2VM3VWbCeTioAZBTBA= +google.golang.org/api v0.185.0 h1:ENEKk1k4jW8SmmaT6RE+ZasxmxezCrD5Vw4npvr+pAU= +google.golang.org/api v0.185.0/go.mod h1:HNfvIkJGlgrIlrbYkAm9W9IdkmKZjOTVh33YltygGbg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -768,12 +770,12 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240604185151-ef581f913117 h1:HCZ6DlkKtCDAtD8ForECsY3tKuaR+p4R3grlK80uCCc= -google.golang.org/genproto v0.0.0-20240604185151-ef581f913117/go.mod h1:lesfX/+9iA+3OdqeCpoDddJaNxVB1AB6tD7EfqMmprc= -google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw= -google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/genproto v0.0.0-20240617180043-68d350f18fd4 h1:CUiCqkPw1nNrNQzCCG4WA65m0nAmQiwXHpub3dNyruU= +google.golang.org/genproto v0.0.0-20240617180043-68d350f18fd4/go.mod h1:EvuUDCulqGgV80RvP1BHuom+smhX4qtlhnNatHuroGQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3 h1:QW9+G6Fir4VcRXVH8x3LilNAb6cxBGLa6+GM4hRwexE= +google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3/go.mod h1:kdrSS/OiLkPrNUpzD4aHgCq2rVuC/YRxok32HXZ4vRE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 h1:Di6ANFilr+S60a4S61ZM00vLdw0IrQOSMS2/6mrnOU0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -794,8 +796,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= diff --git a/pkg/controllers/repositoryserver/repositoryserver_controller_test.go b/pkg/controllers/repositoryserver/repositoryserver_controller_test.go index 01da2a12f6..11130aa423 100644 --- a/pkg/controllers/repositoryserver/repositoryserver_controller_test.go +++ b/pkg/controllers/repositoryserver/repositoryserver_controller_test.go @@ -484,7 +484,7 @@ func (s *RepoServerControllerSuite) waitForRepoServerInfoUpdateInCR(repoServerNa } func (s *RepoServerControllerSuite) waitOnRepositoryServerState(c *C, reposerverName string) (crv1alpha1.RepositoryServerProgress, error) { - ctxTimeout := 5 * time.Minute + ctxTimeout := 10 * time.Minute ctx, cancel := context.WithTimeout(context.Background(), ctxTimeout) defer cancel() var repoServerState crv1alpha1.RepositoryServerProgress diff --git a/pkg/errorchecker/errorchecker.go b/pkg/errorchecker/errorchecker.go new file mode 100644 index 0000000000..80cac2c39d --- /dev/null +++ b/pkg/errorchecker/errorchecker.go @@ -0,0 +1,28 @@ +// Copyright 2024 The Kanister Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package errorchecker + +import ( + "regexp" + + "gopkg.in/check.v1" +) + +// AssertErrorMessage is purposed to verify that error message matches wanted pattern +func AssertErrorMessage(c *check.C, err error, wanted string) { + matches, err := regexp.MatchString("^"+wanted+"$", err.Error()) + c.Assert(err, check.IsNil) + c.Assert(matches, check.Equals, true) +} diff --git a/pkg/errorchecker/errorchecker_test.go b/pkg/errorchecker/errorchecker_test.go new file mode 100644 index 0000000000..7194723fc4 --- /dev/null +++ b/pkg/errorchecker/errorchecker_test.go @@ -0,0 +1,33 @@ +package errorchecker + +import ( + "testing" + + . "gopkg.in/check.v1" + + "github.com/kanisterio/errkit" + "github.com/pkg/errors" +) + +// Hook up gocheck into the "go test" runner. +func Test(t *testing.T) { TestingT(t) } + +type ErrorsTestSuite struct{} + +var _ = Suite(&ErrorsTestSuite{}) + +func (ts *ErrorsTestSuite) TestWrappingAndMatching(c *C) { + errkitErr := errkit.New("Errkit error") + errkitWrappedErr := errkit.Wrap(errkitErr, "errkit wrapped") + errorsWrappedErr := errors.Wrap(errkitWrappedErr, "errors wrapped") + errorsWrappedErr1 := errors.Wrap(errorsWrappedErr, "errors wrapped 1") + + // Ensure that errors from 'errkit' wrapped by the older 'errors' package remain matchable. + c.Assert(errors.Is(errorsWrappedErr, errkitErr), Equals, true) + // Ensure that transformation to string still works + c.Assert(errorsWrappedErr1.Error(), Equals, "errors wrapped 1: errors wrapped: errkit wrapped: Errkit error") + // Ensure that error message matching does work as expected + AssertErrorMessage(c, errorsWrappedErr1, ".*errkit wrapped.*") + AssertErrorMessage(c, errorsWrappedErr1, ".*Errkit error") + AssertErrorMessage(c, errorsWrappedErr1, "errors wrapped 1.*") +} diff --git a/pkg/kopia/cli/args/ephemeral_args.go b/pkg/kopia/cli/args/ephemeral_args.go index d9195eb257..22ea95c2ae 100644 --- a/pkg/kopia/cli/args/ephemeral_args.go +++ b/pkg/kopia/cli/args/ephemeral_args.go @@ -18,6 +18,7 @@ import ( "fmt" "github.com/kanisterio/kanister/pkg/logsafe" + "github.com/kanisterio/safecli/command" ) diff --git a/pkg/kopia/cli/internal/args/args.go b/pkg/kopia/cli/internal/args/args.go index cad732dc9d..bf6606334b 100644 --- a/pkg/kopia/cli/internal/args/args.go +++ b/pkg/kopia/cli/internal/args/args.go @@ -15,9 +15,9 @@ package args import ( - "github.com/kanisterio/safecli/command" - "github.com/kanisterio/kanister/pkg/kopia/cli" + + "github.com/kanisterio/safecli/command" ) // ID creates a new ID argument. diff --git a/pkg/kopia/cli/internal/opts/cache_opts.go b/pkg/kopia/cli/internal/opts/cache_opts.go index fb1222b4af..2c3343518a 100644 --- a/pkg/kopia/cli/internal/opts/cache_opts.go +++ b/pkg/kopia/cli/internal/opts/cache_opts.go @@ -17,9 +17,9 @@ package opts import ( "strconv" - "github.com/kanisterio/safecli/command" - "github.com/kanisterio/kanister/pkg/kopia/cli/args" + + "github.com/kanisterio/safecli/command" ) const ( diff --git a/pkg/kopia/cli/repository/storage/gcs/gcs_opts.go b/pkg/kopia/cli/repository/storage/gcs/gcs_opts.go index d04c89480c..1c6f7577dd 100644 --- a/pkg/kopia/cli/repository/storage/gcs/gcs_opts.go +++ b/pkg/kopia/cli/repository/storage/gcs/gcs_opts.go @@ -15,8 +15,9 @@ package gcs import ( - "github.com/kanisterio/kanister/pkg/kopia/cli" "github.com/kanisterio/safecli/command" + + "github.com/kanisterio/kanister/pkg/kopia/cli" ) var ( diff --git a/pkg/kopia/cli/repository/storage/gcs/gcs_opts_test.go b/pkg/kopia/cli/repository/storage/gcs/gcs_opts_test.go index d45adab333..071d751a44 100644 --- a/pkg/kopia/cli/repository/storage/gcs/gcs_opts_test.go +++ b/pkg/kopia/cli/repository/storage/gcs/gcs_opts_test.go @@ -19,9 +19,10 @@ import ( "gopkg.in/check.v1" - "github.com/kanisterio/kanister/pkg/kopia/cli" "github.com/kanisterio/safecli/command" "github.com/kanisterio/safecli/test" + + "github.com/kanisterio/kanister/pkg/kopia/cli" ) func TestGCSOptions(t *testing.T) { check.TestingT(t) } diff --git a/pkg/kopia/command/parse_command_output.go b/pkg/kopia/command/parse_command_output.go index 1789d64138..8dc25a09e7 100644 --- a/pkg/kopia/command/parse_command_output.go +++ b/pkg/kopia/command/parse_command_output.go @@ -23,10 +23,11 @@ import ( "strings" "github.com/dustin/go-humanize" + + "github.com/kanisterio/errkit" "github.com/kopia/kopia/repo/manifest" "github.com/kopia/kopia/snapshot" "github.com/kopia/kopia/snapshot/policy" - "github.com/pkg/errors" "github.com/kanisterio/kanister/pkg/field" "github.com/kanisterio/kanister/pkg/log" @@ -48,7 +49,7 @@ const ( // SnapshotIDsFromSnapshot extracts root ID of a snapshot from the logs func SnapshotIDsFromSnapshot(output string) (snapID, rootID string, err error) { if output == "" { - return snapID, rootID, errors.New("Received empty output") + return snapID, rootID, errkit.New("Received empty output") } logs := regexp.MustCompile("[\r\n]").Split(output, -1) @@ -62,7 +63,7 @@ func SnapshotIDsFromSnapshot(output string) (snapID, rootID string, err error) { return snapID, rootID, nil } } - return snapID, rootID, errors.New("Failed to find Root ID from output") + return snapID, rootID, errkit.New("Failed to find Root ID from output") } // LatestSnapshotInfoFromManifestList returns snapshot ID and backup path of the latest snapshot from `manifests list` output @@ -73,7 +74,7 @@ func LatestSnapshotInfoFromManifestList(output string) (string, string, error) { err := json.Unmarshal([]byte(output), &manifestList) if err != nil { - return snapID, backupPath, errors.Wrap(err, "Failed to unmarshal manifest list") + return snapID, backupPath, errkit.Wrap(err, "Failed to unmarshal manifest list") } for _, manifest := range manifestList { for key, value := range manifest.Labels { @@ -86,10 +87,10 @@ func LatestSnapshotInfoFromManifestList(output string) (string, string, error) { } } if snapID == "" { - return "", "", errors.New("Failed to get latest snapshot ID from manifest list") + return "", "", errkit.New("Failed to get latest snapshot ID from manifest list") } if backupPath == "" { - return "", "", errors.New("Failed to get latest snapshot backup path from manifest list") + return "", "", errkit.New("Failed to get latest snapshot backup path from manifest list") } return snapID, backupPath, nil } @@ -109,15 +110,15 @@ func SnapshotInfoFromSnapshotCreateOutput(output string) (string, string, error) if snapManifest.RootEntry != nil { rootID = snapManifest.RootEntry.ObjectID.String() if snapManifest.RootEntry.DirSummary != nil && snapManifest.RootEntry.DirSummary.FatalErrorCount > 0 { - return "", "", errors.New(fmt.Sprintf("Error occurred during snapshot creation. Output %s", output)) + return "", "", errkit.New(fmt.Sprintf("Error occurred during snapshot creation. Output %s", output)) } } } if snapID == "" { - return "", "", errors.New(fmt.Sprintf("Failed to get snapshot ID from create snapshot output %s", output)) + return "", "", errkit.New(fmt.Sprintf("Failed to get snapshot ID from create snapshot output %s", output)) } if rootID == "" { - return "", "", errors.New(fmt.Sprintf("Failed to get root ID from create snapshot output %s", output)) + return "", "", errkit.New(fmt.Sprintf("Failed to get root ID from create snapshot output %s", output)) } return snapID, rootID, nil } @@ -126,12 +127,12 @@ func SnapshotInfoFromSnapshotCreateOutput(output string) (string, string, error) // is formatted as the output of a kopia snapshot list --all command. func SnapSizeStatsFromSnapListAll(output string) (totalSizeB int64, numSnapshots int, err error) { if output == "" { - return 0, 0, errors.New("Received empty output") + return 0, 0, errkit.New("Received empty output") } snapList, err := ParseSnapshotManifestList(output) if err != nil { - return 0, 0, errors.Wrap(err, "Parsing snapshot list output as snapshot manifest list") + return 0, 0, errkit.Wrap(err, "Parsing snapshot list output as snapshot manifest list") } totalSizeB = sumSnapshotSizes(snapList) @@ -163,7 +164,7 @@ func ParseSnapshotManifestList(output string) ([]*snapshot.Manifest, error) { snapInfoList := []*snapshot.Manifest{} if err := json.Unmarshal([]byte(output), &snapInfoList); err != nil { - return nil, errors.Wrap(err, "Failed to unmarshal snapshot manifest list") + return nil, errkit.Wrap(err, "Failed to unmarshal snapshot manifest list") } return snapInfoList, nil @@ -435,7 +436,7 @@ func parseKopiaRestoreProgressLine(line string) (stats *RestoreStats) { // size in bytes or an error if parsing is unsuccessful. func RepoSizeStatsFromBlobStatsRaw(blobStats string) (phySizeTotal int64, blobCount int, err error) { if blobStats == "" { - return phySizeTotal, blobCount, errors.New("received empty blob stats string") + return phySizeTotal, blobCount, errkit.New("received empty blob stats string") } sizePattern := regexp.MustCompile(repoTotalSizeFromBlobStatsRegEx) @@ -465,21 +466,21 @@ func RepoSizeStatsFromBlobStatsRaw(blobStats string) (phySizeTotal int64, blobCo } if countStr == "" { - return phySizeTotal, blobCount, errors.New("could not find count field in the blob stats") + return phySizeTotal, blobCount, errkit.New("could not find count field in the blob stats") } if sizeStr == "" { - return phySizeTotal, blobCount, errors.New("could not find size field in the blob stats") + return phySizeTotal, blobCount, errkit.New("could not find size field in the blob stats") } countVal, err := strconv.Atoi(countStr) if err != nil { - return phySizeTotal, blobCount, errors.Wrap(err, fmt.Sprintf("unable to convert parsed count value %s", countStr)) + return phySizeTotal, blobCount, errkit.Wrap(err, fmt.Sprintf("unable to convert parsed count value %s", countStr)) } sizeValBytes, err := strconv.Atoi(sizeStr) if err != nil { - return phySizeTotal, blobCount, errors.Wrap(err, fmt.Sprintf("unable to convert parsed size value %s", countStr)) + return phySizeTotal, blobCount, errkit.Wrap(err, fmt.Sprintf("unable to convert parsed size value %s", countStr)) } return int64(sizeValBytes), countVal, nil @@ -514,7 +515,7 @@ func ErrorsFromOutput(output string) []error { clean := ANSIEscapeCode.ReplaceAllString(l, "") // Strip all ANSI escape codes from line match := kopiaErrorPattern.FindAllStringSubmatch(clean, 1) if len(match) > 0 { - err = append(err, errors.New(match[0][1])) + err = append(err, errkit.New(match[0][1])) } } @@ -526,7 +527,7 @@ func ParsePolicyShow(output string) (policy.Policy, error) { policy := policy.Policy{} if err := json.Unmarshal([]byte(output), &policy); err != nil { - return policy, errors.Wrap(err, "Failed to unmarshal snapshot manifest list") + return policy, errkit.Wrap(err, "Failed to unmarshal snapshot manifest list") } return policy, nil diff --git a/pkg/kopia/command/repository.go b/pkg/kopia/command/repository.go index 20c44aeb37..22ec42d2d4 100644 --- a/pkg/kopia/command/repository.go +++ b/pkg/kopia/command/repository.go @@ -18,7 +18,7 @@ import ( "time" "github.com/go-openapi/strfmt" - "github.com/pkg/errors" + "github.com/kanisterio/errkit" "github.com/kanisterio/kanister/pkg/kopia/cli/args" "github.com/kanisterio/kanister/pkg/kopia/command/storage" @@ -68,7 +68,7 @@ func RepositoryConnectCommand(cmdArgs RepositoryCommandArgs) ([]string, error) { RepoPathPrefix: cmdArgs.RepoPathPrefix, }) if err != nil { - return nil, errors.Wrap(err, "Failed to generate storage args") + return nil, errkit.Wrap(err, "Failed to generate storage args") } if !time.Time(cmdArgs.PITFlag).IsZero() { @@ -106,7 +106,7 @@ func RepositoryCreateCommand(cmdArgs RepositoryCommandArgs) ([]string, error) { RepoPathPrefix: cmdArgs.RepoPathPrefix, }) if err != nil { - return nil, errors.Wrap(err, "Failed to generate storage args") + return nil, errkit.Wrap(err, "Failed to generate storage args") } return stringSliceCommand(command.Combine(bsArgs)), nil diff --git a/pkg/kopia/command/storage/secret_utils.go b/pkg/kopia/command/storage/secret_utils.go index 030b49e2b7..e430dab553 100644 --- a/pkg/kopia/command/storage/secret_utils.go +++ b/pkg/kopia/command/storage/secret_utils.go @@ -18,9 +18,10 @@ import ( "context" "time" - "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" + "github.com/kanisterio/errkit" + crv1alpha1 "github.com/kanisterio/kanister/pkg/apis/cr/v1alpha1" "github.com/kanisterio/kanister/pkg/aws" "github.com/kanisterio/kanister/pkg/blockstorage/azure" @@ -64,7 +65,7 @@ func locationType(m map[string][]byte) repositoryserver.LocType { // list of EnvVar based on secret type func GenerateEnvSpecFromCredentialSecret(s *corev1.Secret, assumeRoleDurationS3 time.Duration) ([]corev1.EnvVar, error) { if s == nil { - return nil, errors.New("Secret cannot be nil") + return nil, errkit.New("Secret cannot be nil") } secType := string(s.Type) switch secType { @@ -111,7 +112,7 @@ func getEnvSpecForAzureCredentialSecret(s *corev1.Secret) ([]corev1.EnvVar, erro if storageEnv != "" { env, err := azure.EnvironmentFromName(storageEnv) if err != nil { - return nil, errors.Wrapf(err, "Failed to get azure environment from name: %s", storageEnv) + return nil, errkit.Wrap(err, "Failed to get azure environment from name", "storageEnv", storageEnv) } blobDomain := "blob." + env.StorageEndpointSuffix // TODO : Check how we can set this env to use value from secret diff --git a/pkg/kopia/command/storage/storage_args.go b/pkg/kopia/command/storage/storage_args.go index c85720e393..4d0b9f3f67 100644 --- a/pkg/kopia/command/storage/storage_args.go +++ b/pkg/kopia/command/storage/storage_args.go @@ -15,7 +15,7 @@ package storage import ( - "fmt" + "github.com/kanisterio/errkit" "github.com/kanisterio/kanister/pkg/logsafe" "github.com/kanisterio/kanister/pkg/secrets/repositoryserver" @@ -47,6 +47,6 @@ func KopiaStorageArgs(params *StorageCommandParams) (logsafe.Cmd, error) { case repositoryserver.LocTypeAzure: return azureArgs(params.Location, params.RepoPathPrefix), nil default: - return nil, fmt.Errorf("unsupported type for the location: %s", LocType) + return nil, errkit.New("unsupported type for the location", "locationType", LocType) } } diff --git a/pkg/kopia/errors/utils.go b/pkg/kopia/errors/utils.go index 0cbfe47615..fbd5e0aac4 100644 --- a/pkg/kopia/errors/utils.go +++ b/pkg/kopia/errors/utils.go @@ -15,62 +15,16 @@ package errors import ( - "bytes" "regexp" "strings" - "github.com/pkg/errors" + "github.com/kanisterio/errkit" ) -type errorList []error - -var _ error = errorList{} - -func (e errorList) String() string { - sep := "" - var buf bytes.Buffer - buf.WriteRune('[') - for _, err := range e { - buf.WriteString(sep) - sep = "," - buf.WriteRune('"') - buf.WriteString(err.Error()) - buf.WriteRune('"') - } - buf.WriteRune(']') - return buf.String() -} - -func (e errorList) Error() string { - return e.String() -} - -// Append creates a new combined error from err1, err2. If either error is nil, -// then the other error is returned. -func Append(err1, err2 error) error { - if err1 == nil { - return err2 - } - if err2 == nil { - return err1 - } - el1, ok1 := err1.(errorList) - el2, ok2 := err2.(errorList) - switch { - case ok1 && ok2: - return append(el1, el2...) - case ok1: - return append(el1, err2) - case ok2: - return append(el2, err1) - } - return errorList{err1, err2} -} - // FirstMatching returns the first error that matches the predicate in a // causal dependency err->Cause()->Cause() .... func FirstMatching(err error, predicate func(error) bool) error { - for ; err != nil; err = errors.Unwrap(err) { + for ; err != nil; err = errkit.Unwrap(err) { if predicate(err) { return err } diff --git a/pkg/kopia/errors/utils_test.go b/pkg/kopia/errors/utils_test.go new file mode 100644 index 0000000000..164d3d79da --- /dev/null +++ b/pkg/kopia/errors/utils_test.go @@ -0,0 +1,49 @@ +// Copyright 2024 The Kanister Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package errors + +import ( + "testing" + + "github.com/kanisterio/errkit" + "github.com/pkg/errors" + . "gopkg.in/check.v1" +) + +// Hook up gocheck into the "go test" runner. +func TestKopiaErrors(t *testing.T) { TestingT(t) } + +type KopiaErrorsTestSuite struct{} + +var _ = Suite(&KopiaErrorsTestSuite{}) + +// TestErrCheck verifies that error types are properly detected after wrapping them +func (s *KopiaErrorsTestSuite) TestErrCheck(c *C) { + origErr := errors.New("Some error") + + errWithMessage := errors.WithMessage(origErr, ErrInvalidPasswordStr) + errWrapped := errkit.Wrap(origErr, ErrInvalidPasswordStr) + + c.Assert(IsInvalidPasswordError(errWithMessage), Equals, true) + c.Assert(IsInvalidPasswordError(errWrapped), Equals, true) + c.Assert(IsRepoNotFoundError(errWrapped), Equals, false) + + permittedErrors := []ErrorType{ErrorInvalidPassword, ErrorRepoNotFound} + c.Assert(CheckKopiaErrors(errWithMessage, permittedErrors), Equals, true) + c.Assert(CheckKopiaErrors(errWrapped, permittedErrors), Equals, true) + + wrongErrors := []ErrorType{ErrorRepoNotFound} + c.Assert(CheckKopiaErrors(errWrapped, wrongErrors), Equals, false) +} diff --git a/pkg/kopia/maintenance/get_maintenance_owner.go b/pkg/kopia/maintenance/get_maintenance_owner.go index 7411d04e07..8299226c4b 100644 --- a/pkg/kopia/maintenance/get_maintenance_owner.go +++ b/pkg/kopia/maintenance/get_maintenance_owner.go @@ -18,11 +18,10 @@ import ( "bytes" "context" "encoding/json" - "fmt" + "github.com/kanisterio/errkit" kopiacli "github.com/kopia/kopia/cli" "github.com/kopia/kopia/repo/manifest" - "github.com/pkg/errors" "github.com/kanisterio/kanister/pkg/format" "github.com/kanisterio/kanister/pkg/kopia/command" @@ -76,7 +75,7 @@ func GetMaintenanceOwnerForConnectedRepository( func parseOwner(output []byte) (string, error) { maintInfo := kopiacli.MaintenanceInfo{} if err := json.Unmarshal(output, &maintInfo); err != nil { - return "", errors.New(fmt.Sprintf("failed to unmarshal maintenance info output: %v", err)) + return "", errkit.Wrap(err, "failed to unmarshal maintenance info output") } return maintInfo.Owner, nil } diff --git a/pkg/kopia/repository/client.go b/pkg/kopia/repository/client.go index 9bc478a367..49ed82e61c 100644 --- a/pkg/kopia/repository/client.go +++ b/pkg/kopia/repository/client.go @@ -22,9 +22,9 @@ import ( "time" "github.com/jpillora/backoff" + "github.com/kanisterio/errkit" "github.com/kopia/kopia/repo" "github.com/kopia/kopia/repo/content" - "github.com/pkg/errors" "github.com/kanisterio/kanister/pkg/kopia" "github.com/kanisterio/kanister/pkg/log" @@ -62,7 +62,7 @@ func ConnectToAPIServer( // Extra fingerprint from the TLS Certificate secret fingerprint, err := kopia.ExtractFingerprintFromCertificate(tlsCert) if err != nil { - return errors.Wrap(err, "Failed to extract fingerprint from Kopia API Server Certificate Secret") + return errkit.Wrap(err, "Failed to extract fingerprint from Kopia API Server Certificate Secret") } serverInfo := &repo.APIServerInfo{ @@ -95,7 +95,12 @@ func ConnectToAPIServer( } return true, nil }) - return errors.Wrap(err, "Failed connecting to the Kopia API Server") + + if err != nil { + return errkit.Wrap(err, "Failed connecting to the Kopia API Server") + } + + return nil } // Open connects to the kopia repository based on the config stored in the config file @@ -104,16 +109,16 @@ func ConnectToAPIServer( func Open(ctx context.Context, configFile, password, purpose string) (repo.RepositoryWriter, error) { repoConfig := repositoryConfigFileName(configFile) if _, err := os.Stat(repoConfig); os.IsNotExist(err) { - return nil, errors.New("Failed find kopia configuration file") + return nil, errkit.New("Failed find kopia configuration file") } r, err := repo.Open(ctx, repoConfig, password, &repo.Options{}) if os.IsNotExist(err) { - return nil, errors.New("Failed to find kopia repository, use `kopia repository create` or kopia repository connect` if already created") + return nil, errkit.New("Failed to find kopia repository, use `kopia repository create` or kopia repository connect` if already created") } if err != nil { - return nil, errors.Wrap(err, "Failed to open kopia repository") + return nil, errkit.Wrap(err, "Failed to open kopia repository") } _, rw, err := r.NewWriter(ctx, repo.WriteSessionOptions{ @@ -121,7 +126,11 @@ func Open(ctx context.Context, configFile, password, purpose string) (repo.Repos OnUpload: func(i int64) {}, }) - return rw, errors.Wrap(err, "Failed to open kopia repository writer") + if err != nil { + return nil, errkit.Wrap(err, "Failed to open kopia repository writer") + } + + return rw, nil } func repositoryConfigFileName(configFile string) string { diff --git a/pkg/kopia/repository/client_test.go b/pkg/kopia/repository/client_test.go index 9c41abb5fc..f6eef44de1 100644 --- a/pkg/kopia/repository/client_test.go +++ b/pkg/kopia/repository/client_test.go @@ -5,8 +5,9 @@ import ( "gopkg.in/check.v1" - "github.com/kanisterio/kanister/pkg/kopia/repository" "github.com/kopia/kopia/repo" + + "github.com/kanisterio/kanister/pkg/kopia/repository" ) func Test(t *testing.T) { check.TestingT(t) } diff --git a/pkg/kopia/repository/connect.go b/pkg/kopia/repository/connect.go index 1469cec8f5..d5e12d5749 100644 --- a/pkg/kopia/repository/connect.go +++ b/pkg/kopia/repository/connect.go @@ -4,7 +4,7 @@ import ( "context" "strings" - "github.com/pkg/errors" + "github.com/kanisterio/errkit" "k8s.io/client-go/kubernetes" "github.com/kanisterio/kanister/pkg/format" @@ -24,7 +24,7 @@ func ConnectToKopiaRepository( ) error { cmd, err := command.RepositoryConnectCommand(cmdArgs) if err != nil { - return errors.Wrap(err, "Failed to generate repository connect command") + return errkit.Wrap(err, "Failed to generate repository connect command") } stdout, stderr, err := kube.Exec(ctx, cli, namespace, pod, container, cmd, nil) @@ -36,15 +36,15 @@ func ConnectToKopiaRepository( switch { case strings.Contains(stderr, kerrors.ErrInvalidPasswordStr): - err = errors.WithMessage(err, kerrors.ErrInvalidPasswordStr) + err = errkit.Wrap(err, kerrors.ErrInvalidPasswordStr) case err != nil && strings.Contains(err.Error(), kerrors.ErrCodeOutOfMemoryStr): - err = errors.WithMessage(err, kerrors.ErrOutOfMemoryStr) + err = errkit.Wrap(err, kerrors.ErrOutOfMemoryStr) case strings.Contains(stderr, kerrors.ErrAccessDeniedStr): - err = errors.WithMessage(err, kerrors.ErrAccessDeniedStr) + err = errkit.Wrap(err, kerrors.ErrAccessDeniedStr) case kerrors.RepoNotInitialized(stderr): - err = errors.WithMessage(err, kerrors.ErrRepoNotFoundStr) + err = errkit.Wrap(err, kerrors.ErrRepoNotFoundStr) case kerrors.BucketDoesNotExist(stderr): - err = errors.WithMessage(err, kerrors.ErrBucketDoesNotExistStr) + err = errkit.Wrap(err, kerrors.ErrBucketDoesNotExistStr) } - return errors.Wrap(err, "Failed to connect to the backup repository") + return errkit.Wrap(err, "Failed to connect to the backup repository") } diff --git a/pkg/kopia/repository/connect_or_create.go b/pkg/kopia/repository/connect_or_create.go index f64bdbcad4..36ac4f3755 100644 --- a/pkg/kopia/repository/connect_or_create.go +++ b/pkg/kopia/repository/connect_or_create.go @@ -19,6 +19,8 @@ import ( "k8s.io/client-go/kubernetes" + "github.com/kanisterio/errkit" + "github.com/kanisterio/kanister/pkg/kopia/command" kerrors "github.com/kanisterio/kanister/pkg/kopia/errors" ) @@ -80,6 +82,6 @@ func ConnectToOrCreateKopiaRepository( return nil } - err = kerrors.Append(err, connectErr) + err = errkit.Append(err, connectErr) return err } diff --git a/pkg/kopia/repository/create.go b/pkg/kopia/repository/create.go index 5c858651ba..9aeb63422c 100644 --- a/pkg/kopia/repository/create.go +++ b/pkg/kopia/repository/create.go @@ -19,7 +19,7 @@ import ( "fmt" "strings" - "github.com/pkg/errors" + "github.com/kanisterio/errkit" "k8s.io/client-go/kubernetes" "github.com/kanisterio/kanister/pkg/format" @@ -43,7 +43,7 @@ func CreateKopiaRepository( ) error { cmd, err := command.RepositoryCreateCommand(cmdArgs) if err != nil { - return errors.Wrap(err, "Failed to generate repository create command") + return errkit.Wrap(err, "Failed to generate repository create command") } stdout, stderr, err := kube.Exec(ctx, cli, namespace, pod, container, cmd, nil) format.Log(pod, container, stdout) @@ -57,7 +57,7 @@ func CreateKopiaRepository( message = message + ": " + kerrors.ErrAccessDeniedStr } if err != nil { - return errors.Wrap(err, message) + return errkit.Wrap(err, message) } if err := setGlobalPolicy( @@ -68,7 +68,7 @@ func CreateKopiaRepository( container, cmdArgs.CommandArgs, ); err != nil { - return errors.Wrap(err, "Failed to set global policy") + return errkit.Wrap(err, "Failed to set global policy") } // Set custom maintenance owner in case of successful repository creation @@ -120,7 +120,7 @@ func setCustomMaintenanceOwner( ) error { nsUID, err := utils.GetNamespaceUID(context.Background(), cli, namespace) if err != nil { - return errors.Wrap(err, "Failed to get namespace UID") + return errkit.Wrap(err, "Failed to get namespace UID") } newOwner := fmt.Sprintf(maintenanceOwnerFormat, username, nsUID) cmd := command.MaintenanceSetOwner(command.MaintenanceSetOwnerCommandArgs{ diff --git a/pkg/kopia/snapshot/snapshot.go b/pkg/kopia/snapshot/snapshot.go index c7d6b9b4df..1ec5421b3c 100644 --- a/pkg/kopia/snapshot/snapshot.go +++ b/pkg/kopia/snapshot/snapshot.go @@ -21,13 +21,13 @@ import ( "strings" "time" + "github.com/kanisterio/errkit" "github.com/kopia/kopia/fs" "github.com/kopia/kopia/repo" "github.com/kopia/kopia/repo/manifest" "github.com/kopia/kopia/snapshot" "github.com/kopia/kopia/snapshot/policy" "github.com/kopia/kopia/snapshot/snapshotfs" - "github.com/pkg/errors" "github.com/kanisterio/kanister/pkg/kopia" "github.com/kanisterio/kanister/pkg/kopia/repository" @@ -48,37 +48,37 @@ func SnapshotSource( previous, err := findPreviousSnapshotManifest(ctx, rep, sourceInfo, nil) if err != nil { - return "", 0, errors.Wrap(err, "Failed to find previous kopia manifests") + return "", 0, errkit.Wrap(err, "Failed to find previous kopia manifests") } policyTree, err := policy.TreeForSource(ctx, rep, sourceInfo) if err != nil { - return "", 0, errors.Wrap(err, "Failed to get kopia policy tree") + return "", 0, errkit.Wrap(err, "Failed to get kopia policy tree") } manifest, err := u.Upload(ctx, rootDir, policyTree, sourceInfo, previous...) if err != nil { - return "", 0, errors.Wrap(err, "Failed to upload the kopia snapshot") + return "", 0, errkit.Wrap(err, "Failed to upload the kopia snapshot") } manifest.Description = description if _, err := snapshot.SaveSnapshot(ctx, rep, manifest); err != nil { - return "", 0, errors.Wrap(err, "Failed to save kopia manifest") + return "", 0, errkit.Wrap(err, "Failed to save kopia manifest") } // TODO: https://github.com/kanisterio/kanister/issues/2441 // _, err = policy.ApplyRetentionPolicy(ctx, rep, sourceInfo, true) // if err != nil { - // return "", 0, errors.Wrap(err, "Failed to apply kopia retention policy") + // return "", 0, errkit.Wrap(err, "Failed to apply kopia retention policy") // } if err = policy.SetManual(ctx, rep, sourceInfo); err != nil { - return "", 0, errors.Wrap(err, "Failed to set manual field in kopia scheduling policy for source") + return "", 0, errkit.Wrap(err, "Failed to set manual field in kopia scheduling policy for source") } if ferr := rep.Flush(ctx); ferr != nil { - return "", 0, errors.Wrap(ferr, "Failed to flush kopia repository") + return "", 0, errkit.Wrap(ferr, "Failed to flush kopia repository") } return reportStatus(ctx, snapshotStartTime, manifest) @@ -98,7 +98,7 @@ func reportStatus(ctx context.Context, snapshotStartTime time.Time, manifest *sn } } if len(errs) != 0 { - return "", 0, errors.New(strings.Join(errs, "\n")) + return "", 0, errkit.New(strings.Join(errs, "\n")) } return string(manifestID), snapSize, nil @@ -108,13 +108,13 @@ func reportStatus(ctx context.Context, snapshotStartTime time.Time, manifest *sn func Delete(ctx context.Context, backupID, path, password string) error { rep, err := repository.Open(ctx, kopia.DefaultClientConfigFilePath, password, pullRepoPurpose) if err != nil { - return errors.Wrap(err, "Failed to open kopia repository") + return errkit.Wrap(err, "Failed to open kopia repository") } // Load the kopia snapshot with the given backupID m, err := snapshot.LoadSnapshot(ctx, rep, manifest.ID(backupID)) if err != nil { - return errors.Wrapf(err, "Failed to load kopia snapshot with ID: %v", backupID) + return errkit.Wrap(err, "Failed to load kopia snapshot with ID", "backupId", backupID) } if err := rep.DeleteManifest(ctx, m.ID); err != nil { return err @@ -127,7 +127,7 @@ func Delete(ctx context.Context, backupID, path, password string) error { func findPreviousSnapshotManifest(ctx context.Context, rep repo.Repository, sourceInfo snapshot.SourceInfo, noLaterThan *fs.UTCTimestamp) ([]*snapshot.Manifest, error) { man, err := snapshot.ListSnapshots(ctx, rep, sourceInfo) if err != nil { - return nil, errors.Wrap(err, "Failed to list previous kopia snapshots") + return nil, errkit.Wrap(err, "Failed to list previous kopia snapshots") } // find latest complete snapshot @@ -158,7 +158,7 @@ func MarshalKopiaSnapshot(snapInfo *SnapshotInfo) (string, error) { } snap, err := json.Marshal(snapInfo) if err != nil { - return "", errors.Wrap(err, "failed to marshal kopia snapshot information") + return "", errkit.Wrap(err, "failed to marshal kopia snapshot information") } return string(snap), nil @@ -168,7 +168,7 @@ func MarshalKopiaSnapshot(snapInfo *SnapshotInfo) (string, error) { func UnmarshalKopiaSnapshot(snapInfoJSON string) (SnapshotInfo, error) { snap := SnapshotInfo{} if err := json.Unmarshal([]byte(snapInfoJSON), &snap); err != nil { - return snap, errors.Wrap(err, "failed to unmarshal kopia snapshot information") + return snap, errkit.Wrap(err, "failed to unmarshal kopia snapshot information") } return snap, snap.Validate() } diff --git a/pkg/kopia/snapshot/stream.go b/pkg/kopia/snapshot/stream.go index f22fb908f3..2c8c244bb0 100644 --- a/pkg/kopia/snapshot/stream.go +++ b/pkg/kopia/snapshot/stream.go @@ -21,13 +21,13 @@ import ( "path/filepath" "sync" + "github.com/kanisterio/errkit" "github.com/kopia/kopia/fs" "github.com/kopia/kopia/fs/localfs" "github.com/kopia/kopia/fs/virtualfs" "github.com/kopia/kopia/snapshot" "github.com/kopia/kopia/snapshot/restore" "github.com/kopia/kopia/snapshot/snapshotfs" - "github.com/pkg/errors" "github.com/kanisterio/kanister/pkg/kopia" "github.com/kanisterio/kanister/pkg/kopia/repository" @@ -58,10 +58,10 @@ type SnapshotInfo struct { // Validate validates SnapshotInfo field values func (si *SnapshotInfo) Validate() error { if si == nil { - return errors.New("kopia snapshotInfo cannot be nil") + return errkit.New("kopia snapshotInfo cannot be nil") } if si.ID == "" { - return errors.New("kopia snapshot ID cannot be empty") + return errkit.New("kopia snapshot ID cannot be empty") } return nil } @@ -72,7 +72,7 @@ func (si *SnapshotInfo) Validate() error { func Write(ctx context.Context, source io.ReadCloser, path, password string) (*SnapshotInfo, error) { rep, err := repository.Open(ctx, kopia.DefaultClientConfigFilePath, password, pushRepoPurpose) if err != nil { - return nil, errors.Wrap(err, "Failed to open kopia repository") + return nil, errkit.Wrap(err, "Failed to open kopia repository") } // If the input `path` provided does not have a parent directory OR @@ -118,12 +118,12 @@ func Write(ctx context.Context, source io.ReadCloser, path, password string) (*S func WriteFile(ctx context.Context, path, sourcePath, password string) (*SnapshotInfo, error) { rep, err := repository.Open(ctx, kopia.DefaultClientConfigFilePath, password, pushRepoPurpose) if err != nil { - return nil, errors.Wrap(err, "Failed to open kopia repository") + return nil, errkit.Wrap(err, "Failed to open kopia repository") } dir, err := filepath.Abs(sourcePath) if err != nil { - return nil, errors.Wrapf(err, "Invalid source path '%s'", sourcePath) + return nil, errkit.Wrap(err, "Invalid source path", "sourcePath", sourcePath) } // Populate the source info with parent path as the source @@ -134,7 +134,7 @@ func WriteFile(ctx context.Context, path, sourcePath, password string) (*Snapsho } rootDir, err := getLocalFSEntry(ctx, sourceInfo.Path) if err != nil { - return nil, errors.Wrap(err, "Unable to get local filesystem entry") + return nil, errkit.Wrap(err, "Unable to get local filesystem entry") } // Setup kopia uploader @@ -157,12 +157,12 @@ func WriteFile(ctx context.Context, path, sourcePath, password string) (*Snapsho func getLocalFSEntry(ctx context.Context, path0 string) (fs.Entry, error) { path, err := resolveSymlink(path0) if err != nil { - return nil, errors.Wrap(err, "resolveSymlink") + return nil, errkit.Wrap(err, "resolveSymlink") } e, err := localfs.NewEntry(path) if err != nil { - return nil, errors.Wrap(err, "can't get local fs entry") + return nil, errkit.Wrap(err, "can't get local fs entry") } return e, nil @@ -171,7 +171,7 @@ func getLocalFSEntry(ctx context.Context, path0 string) (fs.Entry, error) { func resolveSymlink(path string) (string, error) { st, err := os.Lstat(path) if err != nil { - return "", errors.Wrap(err, "stat") + return "", errkit.Wrap(err, "stat") } if (st.Mode() & os.ModeSymlink) == 0 { @@ -186,7 +186,7 @@ func resolveSymlink(path string) (string, error) { func Read(ctx context.Context, target io.Writer, backupID, path, password string) error { rep, err := repository.Open(ctx, kopia.DefaultClientConfigFilePath, password, pullRepoPurpose) if err != nil { - return errors.Wrap(err, "Failed to open kopia repository") + return errkit.Wrap(err, "Failed to open kopia repository") } // Get the kopia object ID belonging to the streaming file @@ -198,31 +198,35 @@ func Read(ctx context.Context, target io.Writer, backupID, path, password string // Open repository object and copy the data to the target r, err := rep.OpenObject(ctx, oid) if err != nil { - return errors.Wrapf(err, "Failed to open kopia object: %v", oid) + return errkit.Wrap(err, "Failed to open kopia object", "oid", oid) } defer r.Close() //nolint:errcheck _, err = copy(target, r) - return errors.Wrap(err, "Failed to copy snapshot data to the target") + if err != nil { + return errkit.Wrap(err, "Failed to copy snapshot data to the target") + } + + return nil } // ReadFile restores a kopia snapshot with the given ID to the given target func ReadFile(ctx context.Context, backupID, target, password string) error { rep, err := repository.Open(ctx, kopia.DefaultClientConfigFilePath, password, pullRepoPurpose) if err != nil { - return errors.Wrap(err, "Failed to open kopia repository") + return errkit.Wrap(err, "Failed to open kopia repository") } rootEntry, err := snapshotfs.FilesystemEntryFromIDWithPath(ctx, rep, backupID, false) if err != nil { - return errors.Wrap(err, "Unable to get filesystem entry") + return errkit.Wrap(err, "Unable to get filesystem entry") } p, err := filepath.Abs(target) if err != nil { - return errors.Wrap(err, "Unable to resolve path") + return errkit.Wrap(err, "Unable to resolve path") } // TODO: Do we want to keep this flags configurable? output := &restore.FilesystemOutput{ @@ -236,7 +240,12 @@ func ReadFile(ctx context.Context, backupID, target, password string) error { _, err = restore.Entry(ctx, rep, output, rootEntry, restore.Options{ Parallel: 8, }) - return errors.Wrap(err, "Failed to copy snapshot data to the target") + + if err != nil { + return errkit.Wrap(err, "Failed to copy snapshot data to the target") + } + + return nil } // bufferPool is a pool of shared buffers used during kopia read diff --git a/pkg/kopia/utils.go b/pkg/kopia/utils.go index 612b40910a..8b5a55004f 100644 --- a/pkg/kopia/utils.go +++ b/pkg/kopia/utils.go @@ -25,12 +25,12 @@ import ( "path/filepath" "strings" + "github.com/kanisterio/errkit" "github.com/kopia/kopia/repo" "github.com/kopia/kopia/repo/manifest" "github.com/kopia/kopia/repo/object" "github.com/kopia/kopia/snapshot" "github.com/kopia/kopia/snapshot/snapshotfs" - "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -70,22 +70,22 @@ const ( func ExtractFingerprintFromCertSecret(ctx context.Context, cli kubernetes.Interface, secretName, secretNamespace string) (string, error) { secret, err := cli.CoreV1().Secrets(secretNamespace).Get(ctx, secretName, metav1.GetOptions{}) if err != nil { - return "", errors.Wrapf(err, "Failed to get Certificate Secret. Secret: %s", secretName) + return "", errkit.Wrap(err, "Failed to get Certificate Secret.", "secretName", secretName) } certBytes, err := json.Marshal(secret.Data[TLSCertificateKey]) if err != nil { - return "", errors.Wrap(err, "Failed to marshal Certificate Secret Data") + return "", errkit.Wrap(err, "Failed to marshal Certificate Secret Data") } var certString string if err := json.Unmarshal([]byte(certBytes), &certString); err != nil { - return "", errors.Wrap(err, "Failed to unmarshal Certificate Secret Data") + return "", errkit.Wrap(err, "Failed to unmarshal Certificate Secret Data") } decodedCertData, err := base64.StdEncoding.DecodeString(certString) if err != nil { - return "", errors.Wrap(err, "Failed to decode Certificate Secret Data") + return "", errkit.Wrap(err, "Failed to decode Certificate Secret Data") } return extractFingerprintFromSliceOfBytes(decodedCertData) @@ -96,12 +96,12 @@ func ExtractFingerprintFromCertSecret(ctx context.Context, cli kubernetes.Interf func extractFingerprintFromSliceOfBytes(pemData []byte) (string, error) { block, rest := pem.Decode([]byte(pemData)) if block == nil || len(rest) > 0 { - return "", errors.New("Failed to PEM Decode Kopia API Server Certificate Secret Data") + return "", errkit.New("Failed to PEM Decode Kopia API Server Certificate Secret Data") } cert, err := x509.ParseCertificate(block.Bytes) if err != nil { - return "", errors.Wrap(err, "Failed to parse X509 Kopia API Server Certificate Secret Data") + return "", errkit.Wrap(err, "Failed to parse X509 Kopia API Server Certificate Secret Data") } fingerprint := sha256.Sum256(cert.Raw) @@ -114,17 +114,17 @@ func ExtractFingerprintFromCertificateJSON(cert string) (string, error) { var certMap map[string]string if err := json.Unmarshal([]byte(cert), &certMap); err != nil { - return "", errors.Wrap(err, "Failed to unmarshal Kopia API Server Certificate Secret Data") + return "", errkit.Wrap(err, "Failed to unmarshal Kopia API Server Certificate Secret Data") } decodedCertData, err := base64.StdEncoding.DecodeString(certMap[TLSCertificateKey]) if err != nil { - return "", errors.Wrap(err, "Failed to base64 decode Kopia API Server Certificate Secret Data") + return "", errkit.Wrap(err, "Failed to base64 decode Kopia API Server Certificate Secret Data") } fingerprint, err := extractFingerprintFromSliceOfBytes(decodedCertData) if err != nil { - return "", errors.Wrap(err, "Failed to extract fingerprint Kopia API Server Certificate Secret Data") + return "", errkit.Wrap(err, "Failed to extract fingerprint Kopia API Server Certificate Secret Data") } return fingerprint, nil @@ -135,7 +135,7 @@ func ExtractFingerprintFromCertificateJSON(cert string) (string, error) { func ExtractFingerprintFromCertificate(cert string) (string, error) { fingerprint, err := extractFingerprintFromSliceOfBytes([]byte(cert)) if err != nil { - return "", errors.Wrap(err, "Failed to extract fingerprint Kopia API Server Certificate Secret Data") + return "", errkit.Wrap(err, "Failed to extract fingerprint Kopia API Server Certificate Secret Data") } return fingerprint, nil @@ -150,22 +150,22 @@ func GetStreamingFileObjectIDFromSnapshot(ctx context.Context, rep repo.Reposito // Load the kopia snapshot with the given backupID m, err := snapshot.LoadSnapshot(ctx, rep, manifest.ID(backupID)) if err != nil { - return object.ID{}, errors.Wrapf(err, "Failed to load kopia snapshot with ID: %v", backupID) + return object.ID{}, errkit.Wrap(err, "Failed to load kopia snapshot with ID", "backupId", backupID) } // root entry of the kopia snapshot is a static directory with filepath.Dir(path) as its path if m.RootEntry == nil { - return object.ID{}, errors.New("No root entry found in kopia manifest") + return object.ID{}, errkit.New("No root entry found in kopia manifest") } rootEntry, err := snapshotfs.SnapshotRoot(rep, m) if err != nil { - return object.ID{}, errors.Wrapf(err, "Failed to get root entry from kopia snapshot with ID: %v", backupID) + return object.ID{}, errkit.Wrap(err, "Failed to get root entry from kopia snapshot with ID", "backupId", backupID) } // Get the nested entry belonging to the backed up streaming file and return its object ID e, err := snapshotfs.GetNestedEntry(ctx, rootEntry, []string{filepath.Base(path)}) if err != nil { - return object.ID{}, errors.Wrapf(err, "Failed to get nested entry from kopia snapshot: %v", filepath.Base(path)) + return object.ID{}, errkit.Wrap(err, "Failed to get nested entry from kopia snapshot", "pathBase", filepath.Base(path)) } return e.(object.HasObjectID).ObjectID(), nil diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 1cf9797da1..ed01bed531 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -15,7 +15,7 @@ package kube import ( - "github.com/pkg/errors" + "github.com/kanisterio/errkit" crdclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" @@ -36,7 +36,7 @@ func ConfigNamespace() (string, error) { cc := newClientConfig() ns, _, err := cc.Namespace() if err != nil { - return "", errors.Wrap(err, "Could not get namespace from config") + return "", errkit.Wrap(err, "Could not get namespace from config") } return ns, nil } diff --git a/pkg/kube/exec.go b/pkg/kube/exec.go index 5a81d46ab1..f91a6ede44 100644 --- a/pkg/kube/exec.go +++ b/pkg/kube/exec.go @@ -22,8 +22,8 @@ import ( "net/url" "strings" + "github.com/kanisterio/errkit" "github.com/kanisterio/kanister/pkg/format" - "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" @@ -151,7 +151,11 @@ func ExecWithOptions(ctx context.Context, kubeCli kubernetes.Interface, options errCh := execStream(ctx, kubeCli, config, options) err = <-errCh - return errors.Wrap(err, "Failed to exec command in pod") + if err != nil { + return errkit.Wrap(err, "Failed to exec command in pod") + } + + return nil } func execStream( diff --git a/pkg/kube/exec_test.go b/pkg/kube/exec_test.go index 21fb8a2f35..a769179491 100644 --- a/pkg/kube/exec_test.go +++ b/pkg/kube/exec_test.go @@ -291,7 +291,7 @@ func (s *ExecSuite) TestKopiaCommand(c *C) { // TestContextTimeout verifies that when context is cancelled during command execution, // execution will be interrupted and proper error will be returned. The stdout, stderr streams should be captured. func (s *ExecSuite) TestContextTimeout(c *C) { - ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) + ctx, cancel := context.WithTimeout(context.Background(), 1000*time.Millisecond) defer cancel() cmd := []string{"sh", "-c", "echo abc && sleep 2 && echo def"} for _, cs := range s.pod.Status.ContainerStatuses { diff --git a/pkg/kube/fake_pod_controller.go b/pkg/kube/fake_pod_controller.go index 06fdc41ab5..cc497a5559 100644 --- a/pkg/kube/fake_pod_controller.go +++ b/pkg/kube/fake_pod_controller.go @@ -19,7 +19,7 @@ import ( "io" "time" - "github.com/pkg/errors" + "github.com/kanisterio/errkit" corev1 "k8s.io/api/core/v1" ) @@ -55,7 +55,7 @@ func (fpc *FakePodController) PodName() string { } func (fpc *FakePodController) Run(ctx context.Context, fn func(context.Context, *corev1.Pod) (map[string]interface{}, error)) (map[string]interface{}, error) { - return nil, errors.New("Not implemented") + return nil, errkit.New("Not implemented") } func (fpc *FakePodController) StartPod(_ context.Context) error { @@ -69,11 +69,11 @@ func (fpc *FakePodController) WaitForPodReady(_ context.Context) error { } func (fpc *FakePodController) WaitForPodCompletion(_ context.Context) error { - return errors.New("Not implemented") + return errkit.New("Not implemented") } func (fpc *FakePodController) StreamPodLogs(_ context.Context) (io.ReadCloser, error) { - return nil, errors.New("Not implemented") + return nil, errkit.New("Not implemented") } func (fpc *FakePodController) GetCommandExecutor() (PodCommandExecutor, error) { diff --git a/pkg/kube/log_reader_test.go b/pkg/kube/log_reader_test.go index 380b7ef307..361e89f31a 100644 --- a/pkg/kube/log_reader_test.go +++ b/pkg/kube/log_reader_test.go @@ -3,7 +3,7 @@ package kube import ( "bytes" "context" - "fmt" + "errors" "io" . "gopkg.in/check.v1" @@ -39,7 +39,7 @@ func (frw *fakeResponseWrapper) Stream(context.Context) (io.ReadCloser, error) { } func (s *LogReaderSuite) TestLogReader(c *C) { - err := fmt.Errorf("TEST") + err := errors.New("TEST") for _, tc := range []struct { rw *fakeResponseWrapper err error diff --git a/pkg/kube/pod.go b/pkg/kube/pod.go index 38086e3cef..bd527ee8ee 100644 --- a/pkg/kube/pod.go +++ b/pkg/kube/pod.go @@ -26,6 +26,7 @@ import ( "github.com/gofrs/uuid" json "github.com/json-iterator/go" + "github.com/kanisterio/errkit" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -87,7 +88,7 @@ func GetPodObjectFromPodOptions(ctx context.Context, cli kubernetes.Interface, o // If Namespace is not specified, use the controller Namespace. cns, err := GetControllerNamespace() if err != nil { - return nil, errors.Wrapf(err, "Failed to get controller namespace") + return nil, errkit.Wrap(err, "Failed to get controller namespace") } ns := opts.Namespace if ns == "" { @@ -100,7 +101,7 @@ func GetPodObjectFromPodOptions(ctx context.Context, cli kubernetes.Interface, o if sa == "" && ns == cns { sa, err = GetControllerServiceAccount(cli) if err != nil { - return nil, errors.Wrap(err, "Failed to get Controller Service Account") + return nil, errkit.Wrap(err, "Failed to get Controller Service Account") } } @@ -110,11 +111,11 @@ func GetPodObjectFromPodOptions(ctx context.Context, cli kubernetes.Interface, o volumeMounts, podVolumes, err := createFilesystemModeVolumeSpecs(ctx, opts.Volumes) if err != nil { - return nil, errors.Wrapf(err, "Failed to create volume spec") + return nil, errkit.Wrap(err, "Failed to create volume spec") } volumeDevices, blockVolumes, err := createBlockModeVolumeSpecs(opts.BlockVolumes) if err != nil { - return nil, errors.Wrapf(err, "Failed to create raw block volume spec") + return nil, errkit.Wrap(err, "Failed to create raw block volume spec") } podVolumes = append(podVolumes, blockVolumes...) defaultSpecs := corev1.PodSpec{ @@ -145,7 +146,7 @@ func GetPodObjectFromPodOptions(ctx context.Context, cli kubernetes.Interface, o // Patch default Pod Specs if needed patchedSpecs, err := patchDefaultPodSpecs(defaultSpecs, opts.PodOverride) if err != nil { - return nil, errors.Wrapf(err, "Failed to create pod. Failed to override pod specs. Namespace: %s, NameFmt: %s", opts.Namespace, opts.GenerateName) + return nil, errkit.Wrap(err, "Failed to create pod. Failed to override pod specs.", "namespace", opts.Namespace, "nameFmt", opts.GenerateName) } // Always put the main container the first @@ -274,7 +275,7 @@ func ContainerNameFromPodOptsOrDefault(po *PodOptions) string { func CreatePod(ctx context.Context, cli kubernetes.Interface, opts *PodOptions) (*corev1.Pod, error) { pod, err := GetPodObjectFromPodOptions(ctx, cli, opts) if err != nil { - return nil, errors.Wrapf(err, "Failed to get pod from podOptions. Namespace: %s, NameFmt: %s", opts.Namespace, opts.GenerateName) + return nil, errkit.Wrap(err, "Failed to get pod from podOptions", "namespace", opts.Namespace, "nameFmt", opts.GenerateName) } log.Debug().WithContext(ctx).Print("Creating POD", field.M{"name": pod.Name, "namespace": pod.Namespace}) @@ -282,7 +283,7 @@ func CreatePod(ctx context.Context, cli kubernetes.Interface, opts *PodOptions) pod, err = cli.CoreV1().Pods(pod.Namespace).Create(ctx, pod, metav1.CreateOptions{}) if err != nil { log.Error().WithContext(ctx).WithError(err).Print("Failed to create pod.", field.M{"pod": getRedactedPod(pod), "options": getRedactedOptions(opts)}) - return nil, errors.Wrapf(err, "Failed to create pod. Namespace: %s, NameFmt: %s", opts.Namespace, opts.GenerateName) + return nil, errkit.Wrap(err, "Failed to create pod", "namespace", opts.Namespace, "nameFmt", opts.GenerateName) } return pod, nil } @@ -321,7 +322,7 @@ func GetPodLogs(ctx context.Context, cli kubernetes.Interface, namespace, podNam func getErrorFromLogs(ctx context.Context, cli kubernetes.Interface, namespace, podName, containerName string, err error, errorMessage string) error { r, logErr := StreamPodLogs(ctx, cli, namespace, podName, containerName) if logErr != nil { - return errors.Wrapf(logErr, "Failed to fetch logs from the pod") + return errkit.Wrap(logErr, "Failed to fetch logs from the pod") } defer r.Close() @@ -330,7 +331,7 @@ func getErrorFromLogs(ctx context.Context, cli kubernetes.Interface, namespace, // We are not interested in log extraction error io.Copy(lt, r) // nolint: errcheck - return errors.Wrap(errors.Wrap(err, lt.ToString()), errorMessage) + return errkit.Wrap(errkit.Wrap(err, lt.ToString()), errorMessage) } // WaitForPodReady waits for a pod to exit the pending state @@ -358,7 +359,7 @@ func WaitForPodReady(ctx context.Context, cli kubernetes.Interface, namespace, n if p.Status.Phase == corev1.PodPending { if p.Status.Reason == "OutOfmemory" || p.Status.Reason == "OutOfcpu" { attachLog = false - return false, errors.Errorf("Pod stuck in pending state, reason: %s", p.Status.Reason) + return false, errkit.New("Pod stuck in pending state", "reason", p.Status.Reason) } } @@ -380,7 +381,7 @@ func WaitForPodReady(ctx context.Context, cli kubernetes.Interface, namespace, n return getErrorFromLogs(ctx, cli, namespace, name, containerForLogs, err, errorMessage) } - return errors.Wrap(err, errorMessage) + return errkit.Wrap(err, errorMessage) } func checkNodesStatus(p *corev1.Pod, cli kubernetes.Interface) error { @@ -388,10 +389,10 @@ func checkNodesStatus(p *corev1.Pod, cli kubernetes.Interface) error { if n[0] != "" { node, err := cli.CoreV1().Nodes().Get(context.TODO(), n[0], metav1.GetOptions{}) if err != nil { - return errors.Wrapf(err, "%s %s", errAccessingNode, n[0]) + return errkit.Wrap(err, errAccessingNode, "node", n[0]) } if !IsNodeReady(node) || !IsNodeSchedulable(node) { - return errors.Errorf("Node %s is currently not ready/schedulable", n[0]) + return errkit.New("Node is currently not ready/schedulable", "node", n[0]) } } return nil @@ -425,14 +426,14 @@ func checkPVCAndPVStatus(ctx context.Context, vol corev1.Volume, p *corev1.Pod, if apierrors.IsNotFound(errors.Cause(err)) { // Do not return err, wait for timeout, since sometimes in case of statefulsets, they trigger creation of a volume return nil - } else { - return errors.Wrapf(err, "Failed to get PVC %s", pvcName) } + + return errkit.Wrap(err, "Failed to get PVC", "pvcName", pvcName) } switch pvc.Status.Phase { case corev1.ClaimLost: - return errors.Errorf("PVC %s associated with pod %s has status: %s", pvcName, p.Name, corev1.ClaimLost) + return errkit.New("PVC associated with pod has unexpected status", "pvcName", pvcName, "podName", p.Name, "status", corev1.ClaimLost) case corev1.ClaimPending: pvName := pvc.Spec.VolumeName if pvName == "" { @@ -444,12 +445,14 @@ func checkPVCAndPVStatus(ctx context.Context, vol corev1.Volume, p *corev1.Pod, if apierrors.IsNotFound(errors.Cause(err)) { // wait for timeout return nil - } else { - return errors.Wrapf(err, "Failed to get PV %s", pvName) } + + return errkit.Wrap(err, "Failed to get PV", "pvName", pvName) } if pv.Status.Phase == corev1.VolumeFailed { - return errors.Errorf("PV %s associated with PVC %s has status: %s message: %s reason: %s namespace: %s", pvName, pvcName, corev1.VolumeFailed, pv.Status.Message, pv.Status.Reason, namespace) + return errkit.New("PV associated with PVC has unexpected status", + "pvName", pvName, "pvcName", pvcName, "status", corev1.VolumeFailed, + "message", pv.Status.Message, "reason", pv.Status.Reason, "namespace", namespace) } } @@ -468,16 +471,20 @@ func WaitForPodCompletion(ctx context.Context, cli kubernetes.Interface, namespa } containerForLogs = p.Spec.Containers[0].Name if p.Status.Phase == corev1.PodFailed { - return false, errors.Errorf("Pod %s failed. Pod status: %s", name, p.Status.String()) + return false, errkit.New("Pod failed", "podName", name, "status", p.Status.String()) } return p.Status.Phase == corev1.PodSucceeded, nil }) + if err == nil { + return err + } + errorMessage := "Pod failed or did not transition into complete state" if attachLog { return getErrorFromLogs(ctx, cli, namespace, name, containerForLogs, err, errorMessage) } - return errors.Wrap(err, errorMessage) + return errkit.Wrap(err, errorMessage) } // use Strategic Merge to patch default pod specs with the passed specs diff --git a/pkg/kube/pod_command_executor_test.go b/pkg/kube/pod_command_executor_test.go index 4bde821c6c..43d60dedd5 100644 --- a/pkg/kube/pod_command_executor_test.go +++ b/pkg/kube/pod_command_executor_test.go @@ -17,11 +17,11 @@ package kube import ( "bytes" "context" + "errors" "os" "sync" "time" - "github.com/pkg/errors" . "gopkg.in/check.v1" "k8s.io/client-go/kubernetes/fake" ) diff --git a/pkg/kube/pod_controller.go b/pkg/kube/pod_controller.go index cd9aa296a4..9580b46098 100644 --- a/pkg/kube/pod_controller.go +++ b/pkg/kube/pod_controller.go @@ -19,7 +19,7 @@ import ( "io" "time" - "github.com/pkg/errors" + "github.com/kanisterio/errkit" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -29,10 +29,10 @@ import ( ) var ( - ErrPodControllerNotInitialized = errors.New("pod has not been initialized") - ErrPodControllerPodAlreadyStarted = errors.New("pod has already been started") - ErrPodControllerPodNotReady = errors.New("pod is not yet ready") - ErrPodControllerPodNotStarted = errors.New("pod is not yet started") + ErrPodControllerNotInitialized = errkit.NewSentinelErr("pod has not been initialized") + ErrPodControllerPodAlreadyStarted = errkit.NewSentinelErr("pod has already been started") + ErrPodControllerPodNotReady = errkit.NewSentinelErr("pod is not yet ready") + ErrPodControllerPodNotStarted = errkit.NewSentinelErr("pod is not yet started") PodControllerDefaultStopTime = 30 * time.Second PodControllerInfiniteStopTime = 0 * time.Second ) @@ -143,17 +143,17 @@ func (p *podController) Pod() *corev1.Pod { // StartPod creates pod and in case of success, it stores pod name for further use. func (p *podController) StartPod(ctx context.Context) error { if p.podName != "" { - return errors.Wrap(ErrPodControllerPodAlreadyStarted, "Failed to create pod") + return errkit.Wrap(ErrPodControllerPodAlreadyStarted, "Failed to create pod") } if p.cli == nil || p.podOptions == nil { - return errors.Wrap(ErrPodControllerNotInitialized, "Failed to create pod") + return errkit.Wrap(ErrPodControllerNotInitialized, "Failed to create pod") } pod, err := p.pcp.CreatePod(ctx, p.podOptions) if err != nil { log.WithError(err).Print("Failed to create pod", field.M{"PodName": p.podOptions.Name, "Namespace": p.podOptions.Namespace}) - return errors.Wrap(err, "Failed to create pod") + return errkit.Wrap(err, "Failed to create pod") } p.pod = pod @@ -171,7 +171,7 @@ func (p *podController) WaitForPodReady(ctx context.Context) error { if err := p.pcp.WaitForPodReady(ctx, p.pod.Namespace, p.pod.Name); err != nil { log.WithError(err).Print("Pod failed to become ready in time", field.M{"PodName": p.podName, "Namespace": p.podOptions.Namespace}) - return errors.Wrap(err, "Pod failed to become ready in time") + return errkit.Wrap(err, "Pod failed to become ready in time") } p.podReady = true @@ -191,7 +191,7 @@ func (p *podController) WaitForPodCompletion(ctx context.Context) error { if err := p.pcp.WaitForPodCompletion(ctx, p.pod.Namespace, p.pod.Name); err != nil { log.WithError(err).Print("Pod failed to complete in time", field.M{"PodName": p.podName, "Namespace": p.podOptions.Namespace}) - return errors.Wrap(err, "Pod failed to complete in time") + return errkit.Wrap(err, "Pod failed to complete in time") } p.podReady = false diff --git a/pkg/kube/pod_controller_test.go b/pkg/kube/pod_controller_test.go index a755ad9e0c..8eaea039a4 100644 --- a/pkg/kube/pod_controller_test.go +++ b/pkg/kube/pod_controller_test.go @@ -16,11 +16,12 @@ package kube import ( "context" + "errors" "fmt" "os" "time" - "github.com/pkg/errors" + "github.com/kanisterio/errkit" . "gopkg.in/check.v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -106,7 +107,7 @@ func (s *PodControllerTestSuite) TestPodControllerWaitPod(c *C) { ctx := context.Background() cli := fake.NewSimpleClientset() - simulatedError := errors.New("SimulatedError") + simulatedError := errkit.Wrap(errors.New("SimulatedError"), "Wrapped") cases := map[string]func(pcp *FakePodControllerProcessor, pc PodController){ "Waiting failed because pod not started yet": func(pcp *FakePodControllerProcessor, pc PodController) { @@ -131,6 +132,7 @@ func (s *PodControllerTestSuite) TestPodControllerWaitPod(c *C) { c.Assert(pcp.InWaitForPodReadyPodName, Equals, podControllerPodName) c.Assert(pcp.InWaitForPodReadyNamespace, Equals, podControllerNS) c.Assert(errors.Is(err, pcp.WaitForPodReadyErr), Equals, true) + c.Assert(err.Error(), Equals, fmt.Sprintf("Pod failed to become ready in time: %s", simulatedError.Error())) // Check that POD deletion was also invoked with expected arguments }, diff --git a/pkg/kube/pod_file_writer.go b/pkg/kube/pod_file_writer.go index 87f3eba410..9247cc51fb 100644 --- a/pkg/kube/pod_file_writer.go +++ b/pkg/kube/pod_file_writer.go @@ -18,7 +18,7 @@ import ( "context" "io" - "github.com/pkg/errors" + "github.com/kanisterio/errkit" "k8s.io/client-go/kubernetes" ) @@ -67,7 +67,7 @@ type podFileWriter struct { func (p *podFileWriter) Write(ctx context.Context, filePath string, content io.Reader) (PodFileRemover, error) { pw := p.fileWriterProcessor.NewPodWriter(filePath, content) if err := pw.Write(ctx, p.namespace, p.podName, p.containerName); err != nil { - return nil, errors.Wrap(err, "Write file to pod failed") + return nil, errkit.Wrap(err, "Write file to pod failed") } return &podFileRemover{ diff --git a/pkg/kube/pod_file_writer_test.go b/pkg/kube/pod_file_writer_test.go index a93656912a..7c9f1dc3be 100644 --- a/pkg/kube/pod_file_writer_test.go +++ b/pkg/kube/pod_file_writer_test.go @@ -17,10 +17,11 @@ package kube import ( "bytes" "context" + "errors" "io" "os" - "github.com/pkg/errors" + "github.com/kanisterio/errkit" . "gopkg.in/check.v1" "k8s.io/client-go/kubernetes/fake" ) @@ -78,7 +79,7 @@ func (s *PodFileWriterTestSuite) TestPodRunnerWriteFile(c *C) { ctx := context.Background() cli := fake.NewSimpleClientset() - simulatedError := errors.New("SimulatedError") + simulatedError := errkit.NewSentinelErr("SimulatedError") cases := map[string]func(pfwp *fakePodFileWriterProcessor, pfw PodFileWriter){ "Write to pod failed": func(pfwp *fakePodFileWriterProcessor, pfw PodFileWriter) { diff --git a/pkg/kube/pod_runner.go b/pkg/kube/pod_runner.go index 2d0c2268dd..9718d41ea9 100644 --- a/pkg/kube/pod_runner.go +++ b/pkg/kube/pod_runner.go @@ -17,7 +17,7 @@ package kube import ( "context" - "github.com/pkg/errors" + "github.com/kanisterio/errkit" "k8s.io/client-go/kubernetes" "github.com/kanisterio/kanister/pkg/consts" @@ -64,7 +64,7 @@ func (p *podRunner) Run( err := p.pc.StartPod(ctx) if err != nil { - return nil, errors.Wrap(err, "Failed to create pod") + return nil, errkit.Wrap(err, "Failed to create pod") } pod := p.pc.Pod() diff --git a/pkg/kube/pod_test.go b/pkg/kube/pod_test.go index 205d3ee46e..cad80991f6 100644 --- a/pkg/kube/pod_test.go +++ b/pkg/kube/pod_test.go @@ -19,12 +19,12 @@ package kube import ( "context" + "errors" "fmt" "os" "strings" "time" - "github.com/pkg/errors" . "gopkg.in/check.v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" diff --git a/pkg/kube/pod_writer.go b/pkg/kube/pod_writer.go index 43bb991e95..3bd60b90df 100644 --- a/pkg/kube/pod_writer.go +++ b/pkg/kube/pod_writer.go @@ -19,7 +19,7 @@ import ( "io" "path/filepath" - "github.com/pkg/errors" + "github.com/kanisterio/errkit" "k8s.io/client-go/kubernetes" "github.com/kanisterio/kanister/pkg/format" @@ -53,11 +53,15 @@ func NewPodWriter(cli kubernetes.Interface, path string, content io.Reader) PodW // Write will create a new file(if not present) and write the provided content to the file func (p *podWriter) Write(ctx context.Context, namespace, podName, containerName string) error { - cmd := []string{"sh", "-c", "cat - > " + p.path} + cmd := []string{"sh", "-c", "cat - > " + p.path + " && :"} stdout, stderr, err := Exec(ctx, p.cli, namespace, podName, containerName, cmd, p.content) format.LogWithCtx(ctx, podName, containerName, stdout) format.LogWithCtx(ctx, podName, containerName, stderr) - return errors.Wrap(err, "Failed to write contents to file") + if err != nil { + return errkit.Wrap(err, "Failed to write contents to file") + } + + return nil } // Remove will delete the file created by Write() func @@ -66,5 +70,9 @@ func (p *podWriter) Remove(ctx context.Context, namespace, podName, containerNam stdout, stderr, err := Exec(ctx, p.cli, namespace, podName, containerName, cmd, nil) format.LogWithCtx(ctx, podName, containerName, stdout) format.LogWithCtx(ctx, podName, containerName, stderr) - return errors.Wrap(err, "Failed to delete file") + if err != nil { + return errkit.Wrap(err, "Failed to delete file") + } + + return nil } diff --git a/pkg/kube/podinfo.go b/pkg/kube/podinfo.go index 179ae15e0f..05c093b7f5 100644 --- a/pkg/kube/podinfo.go +++ b/pkg/kube/podinfo.go @@ -18,9 +18,9 @@ import ( "context" "os" + "github.com/kanisterio/errkit" "k8s.io/client-go/kubernetes" - "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -39,7 +39,7 @@ func GetControllerNamespace() (string, error) { ns, err := os.ReadFile(nsFile) if err != nil { - return "", errors.Wrapf(err, "Failed to read namespace form k8s mounted file") + return "", errkit.Wrap(err, "Failed to read namespace form k8s mounted file") } return string(ns), nil @@ -52,17 +52,17 @@ func GetControllerServiceAccount(k8sclient kubernetes.Interface) (string, error) } ns, err := GetControllerNamespace() if err != nil { - return "", errors.Wrapf(err, "Failed to get Controller namespace") + return "", errkit.Wrap(err, "Failed to get Controller namespace") } podName, err := GetControllerPodName() if err != nil { - return "", errors.Wrapf(err, "Failed to get Controller pod name") + return "", errkit.Wrap(err, "Failed to get Controller pod name") } pod, err := k8sclient.CoreV1().Pods(ns).Get(context.TODO(), podName, metav1.GetOptions{}) if err != nil { - return "", errors.Wrapf(err, "Failed to get Controller pod object from k8s") + return "", errkit.Wrap(err, "Failed to get Controller pod object from k8s") } return pod.Spec.ServiceAccountName, nil } @@ -74,7 +74,7 @@ func GetControllerPodName() (string, error) { } podName, err := os.Hostname() if err != nil { - return "", errors.Wrapf(err, "Failed to get pod name from Hostname") + return "", errkit.Wrap(err, "Failed to get pod name from Hostname") } return podName, nil diff --git a/pkg/kube/snapshot/snapshot.go b/pkg/kube/snapshot/snapshot.go index f489ca8662..379ef4ed04 100644 --- a/pkg/kube/snapshot/snapshot.go +++ b/pkg/kube/snapshot/snapshot.go @@ -18,8 +18,8 @@ import ( "context" "regexp" + "github.com/kanisterio/errkit" v1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" - "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" @@ -134,7 +134,7 @@ func NewSnapshotter(kubeCli kubernetes.Interface, dynCli dynamic.Interface) (Sna // Check if v1 (stable) snapshot API exists exists, err := kube.IsGroupVersionAvailable(ctx, kubeCli.Discovery(), GroupName, Version) if err != nil { - return nil, errors.Errorf("Failed to call discovery APIs: %v", err) + return nil, errkit.Wrap(err, "Failed to call discovery APIs") } if exists { return NewSnapshotStable(kubeCli, dynCli), nil @@ -142,7 +142,7 @@ func NewSnapshotter(kubeCli kubernetes.Interface, dynCli dynamic.Interface) (Sna // Check if v1beta1 snapshot API exists exists, err = kube.IsGroupVersionAvailable(ctx, kubeCli.Discovery(), v1beta1.GroupName, v1beta1.Version) if err != nil { - return nil, errors.Errorf("Failed to call discovery APIs: %v", err) + return nil, errkit.Wrap(err, "Failed to call discovery APIs") } if exists { return NewSnapshotBeta(kubeCli, dynCli), nil @@ -150,12 +150,12 @@ func NewSnapshotter(kubeCli kubernetes.Interface, dynCli dynamic.Interface) (Sna // Check if v1alpha1 snapshot API exists exists, err = kube.IsGroupVersionAvailable(ctx, kubeCli.Discovery(), v1alpha1.GroupName, v1alpha1.Version) if err != nil { - return nil, errors.Errorf("Failed to call discovery APIs: %v", err) + return nil, errkit.Wrap(err, "Failed to call discovery APIs") } if exists { return NewSnapshotAlpha(kubeCli, dynCli), nil } - return nil, errors.New("Snapshot resources not supported") + return nil, errkit.New("Snapshot resources not supported") } // We use regexp to match because errors written in vs.Status.Error.Message are strings diff --git a/pkg/kube/snapshot/snapshot_alpha.go b/pkg/kube/snapshot/snapshot_alpha.go index f6ba688b59..d864a83a33 100644 --- a/pkg/kube/snapshot/snapshot_alpha.go +++ b/pkg/kube/snapshot/snapshot_alpha.go @@ -19,8 +19,8 @@ import ( "encoding/json" "fmt" + "github.com/kanisterio/errkit" v1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" - "github.com/pkg/errors" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -61,7 +61,7 @@ func NewSnapshotAlpha(kubeCli kubernetes.Interface, dynCli dynamic.Interface) Sn func (sna *SnapshotAlpha) CloneVolumeSnapshotClass(ctx context.Context, sourceClassName, targetClassName, newDeletionPolicy string, excludeAnnotations []string) error { usSourceSnapClass, err := sna.dynCli.Resource(v1alpha1.VolSnapClassGVR).Get(ctx, sourceClassName, metav1.GetOptions{}) if err != nil { - return errors.Wrapf(err, "Failed to find source VolumeSnapshotClass: %s", sourceClassName) + return errkit.Wrap(err, "Failed to find source VolumeSnapshotClass", "className", sourceClassName) } sourceSnapClass := v1alpha1.VolumeSnapshotClass{} if err := TransformUnstructured(usSourceSnapClass, &sourceSnapClass); err != nil { @@ -75,8 +75,8 @@ func (sna *SnapshotAlpha) CloneVolumeSnapshotClass(ctx context.Context, sourceCl // Set Annotations/Labels usNew.SetAnnotations(existingAnnotations) usNew.SetLabels(map[string]string{CloneVolumeSnapshotClassLabelName: sourceClassName}) - if _, err = sna.dynCli.Resource(v1alpha1.VolSnapClassGVR).Create(ctx, usNew, metav1.CreateOptions{}); !apierrors.IsAlreadyExists(err) { - return errors.Wrapf(err, "Failed to create VolumeSnapshotClass: %s", targetClassName) + if _, err = sna.dynCli.Resource(v1alpha1.VolSnapClassGVR).Create(ctx, usNew, metav1.CreateOptions{}); err != nil && !apierrors.IsAlreadyExists(err) { + return errkit.Wrap(err, "Failed to create VolumeSnapshotClass", "className", targetClassName) } return nil } @@ -90,9 +90,9 @@ func (sna *SnapshotAlpha) GetVolumeSnapshotClass(ctx context.Context, annotation func (sna *SnapshotAlpha) Create(ctx context.Context, name, namespace, pvcName string, snapshotClass *string, waitForReady bool, labels map[string]string) error { if _, err := sna.kubeCli.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, pvcName, metav1.GetOptions{}); err != nil { if apierrors.IsNotFound(err) { - return errors.Errorf("Failed to find PVC %s, Namespace %s", pvcName, namespace) + return errkit.New("Failed to find PVC", "pvc", pvcName, "namespace", namespace) } - return errors.Errorf("Failed to query PVC %s, Namespace %s: %v", pvcName, namespace, err) + return errkit.Wrap(err, "Failed to query PVC", "pvc", pvcName, "namespace", namespace) } snap := UnstructuredVolumeSnapshotAlpha(name, namespace, pvcName, "", *snapshotClass, blockstorage.SanitizeTags(labels)) if _, err := sna.dynCli.Resource(v1alpha1.VolSnapGVR).Namespace(namespace).Create(ctx, snap, metav1.CreateOptions{}); err != nil { @@ -151,7 +151,7 @@ func TransformUnstructuredSnaphotV1alphaToV1(u *unstructured.Unstructured) (*v1. vsRet := v1.VolumeSnapshot{} meta := vs.ObjectMeta.DeepCopy() if meta == nil { - return nil, fmt.Errorf("Invalid VolumeSnapshotObject: ObjectMeta is nil") + return nil, errkit.New("Invalid VolumeSnapshotObject: ObjectMeta is nil") } vsRet.ObjectMeta = *meta @@ -196,10 +196,10 @@ func (sna *SnapshotAlpha) Delete(ctx context.Context, name, namespace string) (* return nil, nil } if err != nil { - return nil, errors.Wrapf(err, "Failed to find VolumeSnapshot: %s/%s", namespace, name) + return nil, errkit.Wrap(err, "Failed to find VolumeSnapshot", "namespace", namespace, "name", name) } if err := sna.dynCli.Resource(v1alpha1.VolSnapGVR).Namespace(namespace).Delete(ctx, name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) { - return nil, errors.Wrapf(err, "Failed to delete VolumeSnapshot: %s/%s", namespace, name) + return nil, errkit.Wrap(err, "Failed to delete VolumeSnapshot", "namespace", namespace, "name", name) } // If the Snapshot does not exist, that's an acceptable error and we ignore it return snap, nil @@ -208,7 +208,7 @@ func (sna *SnapshotAlpha) Delete(ctx context.Context, name, namespace string) (* // DeleteContent will delete the specified VolumeSnapshotContent func (sna *SnapshotAlpha) DeleteContent(ctx context.Context, name string) error { if err := sna.dynCli.Resource(v1alpha1.VolSnapContentGVR).Delete(ctx, name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) { - return errors.Wrapf(err, "Failed to delete VolumeSnapshotContent: %s", name) + return errkit.Wrap(err, "Failed to delete VolumeSnapshotContent", "name", name) } // If the Snapshot Content does not exist, that's an acceptable error and we ignore it return nil @@ -219,15 +219,15 @@ func (sna *SnapshotAlpha) DeleteContent(ctx context.Context, name string) error func (sna *SnapshotAlpha) Clone(ctx context.Context, name, namespace, cloneName, cloneNamespace string, waitForReady bool, labels map[string]string) error { _, err := sna.Get(ctx, cloneName, cloneNamespace) if err == nil { - return errors.Errorf("Target snapshot already exists in target namespace, Volumesnapshot: %s, Namespace: %s", cloneName, cloneNamespace) + return errkit.New("Target snapshot already exists in target namespace", "name", cloneName, "namespace", cloneNamespace) } if !apierrors.IsNotFound(err) { - return errors.Errorf("Failed to query target Volumesnapshot: %s, Namespace: %s: %v", cloneName, cloneNamespace, err) + return errkit.Wrap(err, "Failed to query target Volumesnapshot", "name", cloneName, "namespace", cloneNamespace) } src, err := sna.GetSource(ctx, name, namespace) if err != nil { - return errors.Errorf("Failed to get source") + return errkit.Wrap(err, "Failed to get source") } return sna.CreateFromSource(ctx, src, cloneName, cloneNamespace, waitForReady, labels) } @@ -236,18 +236,18 @@ func (sna *SnapshotAlpha) Clone(ctx context.Context, name, namespace, cloneName, func (sna *SnapshotAlpha) GetSource(ctx context.Context, snapshotName, namespace string) (*Source, error) { snap, err := sna.Get(ctx, snapshotName, namespace) if err != nil { - return nil, errors.Errorf("Failed to get snapshot, VolumeSnapshot: %s, Error: %v", snapshotName, err) + return nil, errkit.Wrap(err, "Failed to get snapshot", "name", snapshotName, "namespace", namespace) } if snap.Status.ReadyToUse == nil || !*snap.Status.ReadyToUse { - return nil, errors.Errorf("Snapshot is not ready, VolumeSnapshot: %s, Namespace: %s", snapshotName, namespace) + return nil, errkit.New("Snapshot is not ready", "name", snapshotName, "namespace", namespace) } if snap.Status.BoundVolumeSnapshotContentName == nil { - return nil, errors.Errorf("Snapshot does not have content, VolumeSnapshot: %s, Namespace: %s", snapshotName, namespace) + return nil, errkit.New("Snapshot does not have content", "name", snapshotName, "namespace", namespace) } cont, err := sna.getContent(ctx, *snap.Status.BoundVolumeSnapshotContentName) if err != nil { - return nil, errors.Errorf("Failed to get snapshot content, VolumeSnapshot: %s, VolumeSnapshotContent: %s, Error: %v", snapshotName, *snap.Status.BoundVolumeSnapshotContentName, err) + return nil, errkit.Wrap(err, "Failed to get snapshot content", "name", snapshotName, "contentName", *snap.Status.BoundVolumeSnapshotContentName) } src := &Source{ Handle: cont.Spec.CSI.SnapshotHandle, @@ -262,7 +262,7 @@ func (sna *SnapshotAlpha) GetSource(ctx context.Context, snapshotName, namespace func (sna *SnapshotAlpha) CreateFromSource(ctx context.Context, source *Source, snapshotName, namespace string, waitForReady bool, labels map[string]string) error { deletionPolicy, err := sna.getDeletionPolicyFromClass(source.VolumeSnapshotClassName) if err != nil { - return errors.Wrap(err, "Failed to get DeletionPolicy from VolumeSnapshotClass") + return errkit.Wrap(err, "Failed to get DeletionPolicy from VolumeSnapshotClass") } contentName := snapshotName + "-content-" + string(uuid.NewUUID()) snap := UnstructuredVolumeSnapshotAlpha(snapshotName, namespace, "", contentName, source.VolumeSnapshotClassName, blockstorage.SanitizeTags(labels)) @@ -270,7 +270,7 @@ func (sna *SnapshotAlpha) CreateFromSource(ctx context.Context, source *Source, return err } if _, err := sna.dynCli.Resource(v1alpha1.VolSnapGVR).Namespace(namespace).Create(ctx, snap, metav1.CreateOptions{}); err != nil { - return errors.Errorf("Failed to create content, Volumesnapshot: %s, Error: %v", snap.GetName(), err) + return errkit.Wrap(err, "Failed to create content", "name", snap.GetName(), "namespace", namespace) } if !waitForReady { return nil @@ -286,12 +286,12 @@ func (sna *SnapshotAlpha) UpdateVolumeSnapshotStatusAlpha(ctx context.Context, n } status, ok := us.Object["status"].(map[string]interface{}) if !ok { - return errors.Errorf("Failed to convert status to map, Volumesnapshot: %s, Status: %v", snapshotName, status) + return errkit.New("Failed to convert status to map", "name", snapshotName, "status", status) } status["readyToUse"] = readyToUse us.Object["status"] = status if _, err := sna.dynCli.Resource(v1alpha1.VolSnapGVR).Namespace(namespace).UpdateStatus(ctx, us, metav1.UpdateOptions{}); err != nil { - return errors.Errorf("Failed to update status, Volumesnapshot: %s, Error: %v", snapshotName, err) + return errkit.Wrap(err, "Failed to update status", "name", snapshotName) } return nil } @@ -300,7 +300,7 @@ func (sna *SnapshotAlpha) UpdateVolumeSnapshotStatusAlpha(ctx context.Context, n func (sna *SnapshotAlpha) CreateContentFromSource(ctx context.Context, source *Source, contentName, snapshotName, namespace, deletionPolicy string) error { content := UnstructuredVolumeSnapshotContentAlpha(contentName, snapshotName, namespace, deletionPolicy, source.Driver, source.Handle, source.VolumeSnapshotClassName) if _, err := sna.dynCli.Resource(v1alpha1.VolSnapContentGVR).Create(ctx, content, metav1.CreateOptions{}); err != nil { - return errors.Errorf("Failed to create content, VolumesnapshotContent: %s, Error: %v", content.GetName(), err) + return errkit.Wrap(err, "Failed to create content", "contentName", content.GetName()) } return nil } @@ -312,7 +312,7 @@ func isReadyToUseAlpha(us *unstructured.Unstructured) (bool, error) { } // Error can be set while waiting for creation if vs.Status.Error != nil { - return false, errors.New(vs.Status.Error.Message) + return false, errkit.New(vs.Status.Error.Message) } return (vs.Status.ReadyToUse && vs.Status.CreationTime != nil), nil } @@ -344,7 +344,7 @@ func (sna *SnapshotAlpha) getContent(ctx context.Context, contentName string) (* func (sna *SnapshotAlpha) getDeletionPolicyFromClass(snapClassName string) (string, error) { us, err := sna.dynCli.Resource(v1alpha1.VolSnapClassGVR).Get(context.TODO(), snapClassName, metav1.GetOptions{}) if err != nil { - return "", errors.Wrapf(err, "Failed to find VolumeSnapshotClass: %s", snapClassName) + return "", errkit.Wrap(err, "Failed to find VolumeSnapshotClass", "className", snapClassName) } vsc := v1alpha1.VolumeSnapshotClass{} if err := TransformUnstructured(us, &vsc); err != nil { @@ -435,15 +435,19 @@ func UnstructuredVolumeSnapshotClassAlpha(name, driver, deletionPolicy string, p // TransformUnstructured maps Unstructured object to object pointed by obj func TransformUnstructured(u *unstructured.Unstructured, obj metav1.Object) error { if u == nil { - return errors.Errorf("Cannot deserialize nil unstructured") + return errkit.New("Cannot deserialize nil unstructured") } b, err := json.Marshal(u.Object) if err != nil { gvk := u.GetObjectKind().GroupVersionKind() - return errors.Wrapf(err, "Failed to Marshal unstructured object GroupVersionKind: %v", gvk) + return errkit.Wrap(err, "Failed to Marshal unstructured object GroupVersionKind", "unstructured", gvk) } err = json.Unmarshal(b, obj) - return errors.Wrapf(err, "Failed to Unmarshal unstructured object") + if err != nil { + return errkit.Wrap(err, "Failed to Unmarshal unstructured object") + } + + return nil } // GetSnapshotClassbyAnnotation checks if the provided annotation is present in either the storageclass @@ -452,49 +456,49 @@ func GetSnapshotClassbyAnnotation(ctx context.Context, dynCli dynamic.Interface, // fetch storageClass sc, err := kubeCli.StorageV1().StorageClasses().Get(ctx, storageClass, metav1.GetOptions{}) if err != nil { - return "", errors.Errorf("Failed to find StorageClass (%s) in the cluster: %v", storageClass, err) + return "", errkit.Wrap(err, "Failed to find StorageClass in the cluster", "class", storageClass) } // Check if storageclass annotation override is present. if val, ok := sc.Annotations[annotationKey]; ok { vsc, err := dynCli.Resource(gvr).Get(ctx, val, metav1.GetOptions{}) if err != nil { - return "", errors.Errorf("Failed to get VolumeSnapshotClass (%s) specified in Storageclass (%s) annotations: %v", val, sc.Name, err) + return "", errkit.Wrap(err, "Failed to get VolumeSnapshotClass specified in Storageclass annotations", "snapshotClass", val, "storageClass", sc.Name) } return vsc.GetName(), nil } us, err := dynCli.Resource(gvr).List(ctx, metav1.ListOptions{}) if err != nil { - return "", errors.Errorf("Failed to get VolumeSnapshotClasses in the cluster: %v", err) + return "", errkit.Wrap(err, "Failed to get VolumeSnapshotClasses in the cluster") } if us == nil || len(us.Items) == 0 { - return "", errors.Errorf("Failed to find any VolumeSnapshotClass in the cluster: %v", err) + return "", errkit.New("Failed to find any VolumeSnapshotClass in the cluster") } for _, vsc := range us.Items { ans := vsc.GetAnnotations() driver, err := getDriverFromUnstruturedVSC(vsc) if err != nil { - return "", errors.Errorf("Failed to get driver for VolumeSnapshotClass (%s): %v", vsc.GetName(), err) + return "", errkit.Wrap(err, "Failed to get driver for VolumeSnapshotClass", "className", vsc.GetName()) } if val, ok := ans[annotationKey]; ok && val == annotationValue && driver == sc.Provisioner { return vsc.GetName(), nil } } - return "", errors.Errorf("Failed to find VolumeSnapshotClass with %s=%s annotation in the cluster", annotationKey, annotationValue) + return "", errkit.New("Failed to find VolumeSnapshotClass with annotation in the cluster", "annotationKey", annotationKey, "annotationValue", annotationValue) } func getDriverFromUnstruturedVSC(uVSC unstructured.Unstructured) (string, error) { if uVSC.GetKind() != VolSnapClassKind { - return "", errors.Errorf("Cannot get diver for %s kind", uVSC.GetKind()) + return "", errkit.New("Cannot get diver for kind", "kind", uVSC.GetKind()) } driver, ok := uVSC.Object[VolSnapClassAlphaDriverKey] if !ok { driver, ok = uVSC.Object[VolSnapClassBetaDriverKey] } if !ok { - return "", errors.Errorf("VolumeSnapshotClass (%s) missing driver/snapshotter field", uVSC.GetName()) + return "", errkit.New("VolumeSnapshotClass missing driver/snapshotter field", "volumeSnapshotClass", uVSC.GetName()) } if driverString, ok := driver.(string); ok { return driverString, nil } - return "", errors.Errorf("Failed to convert driver to string") + return "", errkit.New("Failed to convert driver to string") } diff --git a/pkg/kube/snapshot/snapshot_beta.go b/pkg/kube/snapshot/snapshot_beta.go index 08b6549175..e69fc6b25f 100644 --- a/pkg/kube/snapshot/snapshot_beta.go +++ b/pkg/kube/snapshot/snapshot_beta.go @@ -18,8 +18,8 @@ import ( "context" "fmt" + "github.com/kanisterio/errkit" v1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" - "github.com/pkg/errors" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -50,7 +50,7 @@ func (sna *SnapshotBeta) CloneVolumeSnapshotClass(ctx context.Context, sourceCla func cloneSnapshotClass(ctx context.Context, dynCli dynamic.Interface, snapClassGVR schema.GroupVersionResource, sourceClassName, targetClassName, newDeletionPolicy string, excludeAnnotations []string) error { usSourceSnapClass, err := dynCli.Resource(snapClassGVR).Get(ctx, sourceClassName, metav1.GetOptions{}) if err != nil { - return errors.Wrapf(err, "Failed to find source VolumeSnapshotClass: %s", sourceClassName) + return errkit.Wrap(err, "Failed to find source VolumeSnapshotClass", "volumeSnapshotClass", sourceClassName) } sourceSnapClass := v1.VolumeSnapshotClass{} @@ -65,8 +65,8 @@ func cloneSnapshotClass(ctx context.Context, dynCli dynamic.Interface, snapClass // Set Annotations/Labels usNew.SetAnnotations(existingAnnotations) usNew.SetLabels(map[string]string{CloneVolumeSnapshotClassLabelName: sourceClassName}) - if _, err = dynCli.Resource(snapClassGVR).Create(ctx, usNew, metav1.CreateOptions{}); !apierrors.IsAlreadyExists(err) { - return errors.Wrapf(err, "Failed to create VolumeSnapshotClass: %s", targetClassName) + if _, err = dynCli.Resource(snapClassGVR).Create(ctx, usNew, metav1.CreateOptions{}); err != nil && !apierrors.IsAlreadyExists(err) { + return errkit.Wrap(err, "Failed to create VolumeSnapshotClass", "volumeSnapshotClass", targetClassName) } return nil } @@ -95,14 +95,14 @@ func createSnapshot( ) error { if _, err := kubeCli.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, volumeName, metav1.GetOptions{}); err != nil { if apierrors.IsNotFound(err) { - return errors.Errorf("Failed to find PVC %s, Namespace %s", volumeName, namespace) + return errkit.New("Failed to find PVC", "pvc", volumeName, "namespace", namespace) } - return errors.Wrapf(err, "Failed to query PVC %s, Namespace %s", volumeName, namespace) + return errkit.Wrap(err, "Failed to query PVC", "pvc", volumeName, "namespace", namespace) } snap := UnstructuredVolumeSnapshot(snapGVR, name, namespace, volumeName, "", *snapshotClass, blockstorage.SanitizeTags(labels)) if _, err := dynCli.Resource(snapGVR).Namespace(namespace).Create(ctx, snap, metav1.CreateOptions{}); err != nil { - return errors.Wrapf(err, "Failed to create snapshot resource %s, Namespace %s", name, namespace) + return errkit.Wrap(err, "Failed to create snapshot resource", "name", name, "namespace", namespace) } if !waitForReady { @@ -171,10 +171,10 @@ func deleteSnapshot(ctx context.Context, dynCli dynamic.Interface, snapGVR schem return nil, nil } if err != nil { - return nil, errors.Wrapf(err, "Failed to find VolumeSnapshot: %s/%s", namespace, name) + return nil, errkit.Wrap(err, "Failed to find VolumeSnapshot", "namespace", namespace, "name", name) } if err := dynCli.Resource(snapGVR).Namespace(namespace).Delete(ctx, name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) { - return nil, errors.Wrapf(err, "Failed to delete VolumeSnapshot: %s/%s", namespace, name) + return nil, errkit.Wrap(err, "Failed to delete VolumeSnapshot", "namespace", namespace, "name", name) } // If the Snapshot does not exist, that's an acceptable error and we ignore it return snap, nil @@ -183,7 +183,7 @@ func deleteSnapshot(ctx context.Context, dynCli dynamic.Interface, snapGVR schem // DeleteContent will delete the specified VolumeSnapshotContent func (sna *SnapshotBeta) DeleteContent(ctx context.Context, name string) error { if err := sna.dynCli.Resource(v1beta1.VolSnapContentGVR).Delete(ctx, name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) { - return errors.Wrapf(err, "Failed to delete VolumeSnapshotContent: %s", name) + return errkit.Wrap(err, "Failed to delete VolumeSnapshotContent", "name", name) } // If the Snapshot Content does not exist, that's an acceptable error and we ignore it return nil @@ -194,15 +194,15 @@ func (sna *SnapshotBeta) DeleteContent(ctx context.Context, name string) error { func (sna *SnapshotBeta) Clone(ctx context.Context, name, namespace, cloneName, cloneNamespace string, waitForReady bool, labels map[string]string) error { _, err := sna.Get(ctx, cloneName, cloneNamespace) if err == nil { - return errors.Errorf("Target snapshot already exists in target namespace, Volumesnapshot: %s, Namespace: %s", cloneName, cloneNamespace) + return errkit.New("Target snapshot already exists in target namespace", "volumeSnapshot", cloneName, "namespace", cloneNamespace) } if !apierrors.IsNotFound(err) { - return errors.Errorf("Failed to query target Volumesnapshot: %s, Namespace: %s: %v", cloneName, cloneNamespace, err) + return errkit.Wrap(err, "Failed to query target Volumesnapshot", "volumeSnapshot", cloneName, "namespace", cloneNamespace) } src, err := sna.GetSource(ctx, name, namespace) if err != nil { - return errors.Errorf("Failed to get source") + return errkit.New("Failed to get source") } return sna.CreateFromSource(ctx, src, cloneName, cloneNamespace, waitForReady, labels) } @@ -215,18 +215,18 @@ func (sna *SnapshotBeta) GetSource(ctx context.Context, snapshotName, namespace func getSnapshotSource(ctx context.Context, dynCli dynamic.Interface, snapGVR, snapContentGVR schema.GroupVersionResource, snapshotName, namespace string) (*Source, error) { snap, err := getSnapshot(ctx, dynCli, snapGVR, snapshotName, namespace) if err != nil { - return nil, errors.Errorf("Failed to get snapshot, VolumeSnapshot: %s, Error: %v", snapshotName, err) + return nil, errkit.Wrap(err, "Failed to get snapshot", "volumeSnapshot", snapshotName) } if snap.Status.ReadyToUse == nil || !*snap.Status.ReadyToUse { - return nil, errors.Errorf("Snapshot is not ready, VolumeSnapshot: %s, Namespace: %s", snapshotName, namespace) + return nil, errkit.New("Snapshot is not ready", "volumeSnapshot", snapshotName, "namespace", namespace) } if snap.Status.BoundVolumeSnapshotContentName == nil { - return nil, errors.Errorf("Snapshot does not have content, VolumeSnapshot: %s, Namespace: %s", snapshotName, namespace) + return nil, errkit.New("Snapshot does not have content", "volumeSnapshot", snapshotName, "namespace", namespace) } cont, err := getSnapshotContent(ctx, dynCli, snapContentGVR, *snap.Status.BoundVolumeSnapshotContentName) if err != nil { - return nil, errors.Errorf("Failed to get snapshot content, VolumeSnapshot: %s, VolumeSnapshotContent: %s, Error: %v", snapshotName, *snap.Status.BoundVolumeSnapshotContentName, err) + return nil, errkit.Wrap(err, "Failed to get snapshot content", "volumeSnapshot", snapshotName, "volumeSnapshotContent", *snap.Status.BoundVolumeSnapshotContentName) } src := &Source{ @@ -242,7 +242,7 @@ func getSnapshotSource(ctx context.Context, dynCli dynamic.Interface, snapGVR, s func (sna *SnapshotBeta) CreateFromSource(ctx context.Context, source *Source, snapshotName, namespace string, waitForReady bool, labels map[string]string) error { deletionPolicy, err := getDeletionPolicyFromClass(sna.dynCli, v1beta1.VolSnapClassGVR, source.VolumeSnapshotClassName) if err != nil { - return errors.Wrap(err, "Failed to get DeletionPolicy from VolumeSnapshotClass") + return errkit.Wrap(err, "Failed to get DeletionPolicy from VolumeSnapshotClass") } contentName := snapshotName + "-content-" + string(uuid.NewUUID()) snap := UnstructuredVolumeSnapshot(v1beta1.VolSnapGVR, snapshotName, namespace, "", contentName, source.VolumeSnapshotClassName, blockstorage.SanitizeTags(labels)) @@ -251,7 +251,7 @@ func (sna *SnapshotBeta) CreateFromSource(ctx context.Context, source *Source, s return err } if _, err := sna.dynCli.Resource(v1beta1.VolSnapGVR).Namespace(namespace).Create(ctx, snap, metav1.CreateOptions{}); err != nil { - return errors.Errorf("Failed to create content, Volumesnapshot: %s, Error: %v", snap.GetName(), err) + return errkit.Wrap(err, "Failed to create content", "volumeSnapshot", snap.GetName()) } if !waitForReady { return nil @@ -279,12 +279,12 @@ func updateVolumeSnapshotStatus(ctx context.Context, dynCli dynamic.Interface, s } status, ok := us.Object["status"].(map[string]interface{}) if !ok { - return errors.Errorf("Failed to convert status to map, Volumesnapshot: %s, Status: %v", snapshotName, status) + return errkit.New("Failed to convert status to map", "volumeSnapshot", snapshotName, "status", status) } status["readyToUse"] = readyToUse us.Object["status"] = status if _, err := dynCli.Resource(snapGVR).Namespace(namespace).UpdateStatus(ctx, us, metav1.UpdateOptions{}); err != nil { - return errors.Errorf("Failed to update status, Volumesnapshot: %s, Error: %v", snapshotName, err) + return errkit.Wrap(err, "Failed to update status", "volumeSnapshot", snapshotName) } return nil } @@ -293,7 +293,7 @@ func updateVolumeSnapshotStatus(ctx context.Context, dynCli dynamic.Interface, s func (sna *SnapshotBeta) CreateContentFromSource(ctx context.Context, source *Source, contentName, snapshotName, namespace, deletionPolicy string) error { content := UnstructuredVolumeSnapshotContent(v1beta1.VolSnapContentGVR, contentName, snapshotName, namespace, deletionPolicy, source.Driver, source.Handle, source.VolumeSnapshotClassName) if _, err := sna.dynCli.Resource(v1beta1.VolSnapContentGVR).Create(ctx, content, metav1.CreateOptions{}); err != nil { - return errors.Errorf("Failed to create content, VolumesnapshotContent: %s, Error: %v", content.GetName(), err) + return errkit.Wrap(err, "Failed to create content", "volumeSnapshotContent", content.GetName()) } return nil } @@ -314,7 +314,7 @@ func isReadyToUseBeta(us *unstructured.Unstructured) (bool, error) { } // Error can be set while waiting for creation if vs.Status.Error != nil { - return false, errors.New(*vs.Status.Error.Message) + return false, errkit.New(*vs.Status.Error.Message) } return (vs.Status.ReadyToUse != nil && *vs.Status.ReadyToUse && vs.Status.CreationTime != nil), nil } @@ -334,7 +334,7 @@ func getSnapshotContent(ctx context.Context, dynCli dynamic.Interface, snapConte func getDeletionPolicyFromClass(dynCli dynamic.Interface, snapClassGVR schema.GroupVersionResource, snapClassName string) (string, error) { us, err := dynCli.Resource(snapClassGVR).Get(context.TODO(), snapClassName, metav1.GetOptions{}) if err != nil { - return "", errors.Wrapf(err, "Failed to find VolumeSnapshotClass: %s", snapClassName) + return "", errkit.Wrap(err, "Failed to find VolumeSnapshotClass", "volumeSnapshotClass", snapClassName) } vsc := v1beta1.VolumeSnapshotClass{} if err := TransformUnstructured(us, &vsc); err != nil { diff --git a/pkg/kube/snapshot/snapshot_stable.go b/pkg/kube/snapshot/snapshot_stable.go index 27e2969c3c..d3a28301a8 100644 --- a/pkg/kube/snapshot/snapshot_stable.go +++ b/pkg/kube/snapshot/snapshot_stable.go @@ -17,9 +17,9 @@ package snapshot import ( "context" + "github.com/kanisterio/errkit" "github.com/kanisterio/kanister/pkg/blockstorage" v1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" - "github.com/pkg/errors" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -90,7 +90,7 @@ func (sna *SnapshotStable) Delete(ctx context.Context, name, namespace string) ( // DeleteContent will delete the specified VolumeSnapshotContent func (sna *SnapshotStable) DeleteContent(ctx context.Context, name string) error { if err := sna.dynCli.Resource(VolSnapContentGVR).Delete(ctx, name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) { - return errors.Wrapf(err, "Failed to delete VolumeSnapshotContent: %s", name) + return errkit.Wrap(err, "Failed to delete", "volumeSnapshotContent", name) } // If the Snapshot Content does not exist, that's an acceptable error and we ignore it return nil @@ -101,15 +101,15 @@ func (sna *SnapshotStable) DeleteContent(ctx context.Context, name string) error func (sna *SnapshotStable) Clone(ctx context.Context, name, namespace, cloneName, cloneNamespace string, waitForReady bool, labels map[string]string) error { _, err := sna.Get(ctx, cloneName, cloneNamespace) if err == nil { - return errors.Errorf("Target snapshot already exists in target namespace, Volumesnapshot: %s, Namespace: %s", cloneName, cloneNamespace) + return errkit.New("Target snapshot already exists in target namespace", "volumeSnapshot", cloneName, "namespace", cloneNamespace) } if !apierrors.IsNotFound(err) { - return errors.Errorf("Failed to query target Volumesnapshot: %s, Namespace: %s: %v", cloneName, cloneNamespace, err) + return errkit.Wrap(err, "Failed to query target", "volumeSnapshot", cloneName, "namespace", cloneNamespace) } src, err := sna.GetSource(ctx, name, namespace) if err != nil { - return errors.Errorf("Failed to get source") + return errkit.New("Failed to get source") } return sna.CreateFromSource(ctx, src, cloneName, cloneNamespace, waitForReady, labels) } @@ -123,7 +123,7 @@ func (sna *SnapshotStable) GetSource(ctx context.Context, snapshotName, namespac func (sna *SnapshotStable) CreateFromSource(ctx context.Context, source *Source, snapshotName, namespace string, waitForReady bool, labels map[string]string) error { deletionPolicy, err := getDeletionPolicyFromClass(sna.dynCli, VolSnapClassGVR, source.VolumeSnapshotClassName) if err != nil { - return errors.Wrap(err, "Failed to get DeletionPolicy from VolumeSnapshotClass") + return errkit.Wrap(err, "Failed to get DeletionPolicy from VolumeSnapshotClass") } contentName := snapshotName + "-content-" + string(uuid.NewUUID()) snap := UnstructuredVolumeSnapshot( @@ -140,7 +140,7 @@ func (sna *SnapshotStable) CreateFromSource(ctx context.Context, source *Source, return err } if _, err := sna.dynCli.Resource(VolSnapGVR).Namespace(namespace).Create(ctx, snap, metav1.CreateOptions{}); err != nil { - return errors.Errorf("Failed to create content, Volumesnapshot: %s, Error: %v", snap.GetName(), err) + return errkit.Wrap(err, "Failed to create content", "volumeSnapshot", snap.GetName()) } if !waitForReady { return nil @@ -158,7 +158,7 @@ func (sna *SnapshotStable) UpdateVolumeSnapshotStatusStable(ctx context.Context, func (sna *SnapshotStable) CreateContentFromSource(ctx context.Context, source *Source, contentName, snapshotName, namespace, deletionPolicy string) error { content := UnstructuredVolumeSnapshotContent(VolSnapContentGVR, contentName, snapshotName, namespace, deletionPolicy, source.Driver, source.Handle, source.VolumeSnapshotClassName) if _, err := sna.dynCli.Resource(VolSnapContentGVR).Create(ctx, content, metav1.CreateOptions{}); err != nil { - return errors.Errorf("Failed to create content, VolumesnapshotContent: %s, Error: %v", content.GetName(), err) + return errkit.Wrap(err, "Failed to create content", "volumeSnapshotContent", content.GetName()) } return nil } diff --git a/pkg/kube/utils.go b/pkg/kube/utils.go index 854e16683c..40eba84db0 100644 --- a/pkg/kube/utils.go +++ b/pkg/kube/utils.go @@ -16,8 +16,8 @@ package kube import ( "context" - "fmt" + "github.com/kanisterio/errkit" osversioned "github.com/openshift/client-go/apps/clientset/versioned" corev1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes" @@ -49,7 +49,7 @@ func GetPodContainerFromDeployment(ctx context.Context, cli kubernetes.Interface return podName, containerName, err } if len(pod) == 0 { - return podName, containerName, fmt.Errorf("Unable to find ready pod for deployment %s/%s", namespace, deployName) + return podName, containerName, errkit.New("Unable to find ready pod for deployment", "namespace", namespace, "deployment", deployName) } podName = pod[0].GetName() container, err := PodContainers(ctx, cli, namespace, podName) @@ -57,7 +57,7 @@ func GetPodContainerFromDeployment(ctx context.Context, cli kubernetes.Interface return podName, containerName, err } if len(container) == 0 { - return podName, containerName, fmt.Errorf("Unable to find containers in pod %s/%s", namespace, podName) + return podName, containerName, errkit.New("Unable to find containers in pod", "namespace", namespace, "podName", podName) } return podName, container[0].Name, nil } @@ -69,7 +69,7 @@ func GetPodContainerFromDeploymentConfig(ctx context.Context, osCli osversioned. return podName, containerName, err } if len(pods) == 0 { - return podName, containerName, fmt.Errorf("Unable to find ready pod for deploymentconfig %s/%s", namespace, deployConfigName) + return podName, containerName, errkit.New("Unable to find ready pod for deploymentconfig", "namespace", namespace, "deploymentConfig", deployConfigName) } podName = pods[0].GetName() @@ -79,7 +79,7 @@ func GetPodContainerFromDeploymentConfig(ctx context.Context, osCli osversioned. } if len(containers) == 0 { - return podName, containerName, fmt.Errorf("Unable to find containers in pod %s/%s", namespace, podName) + return podName, containerName, errkit.New("Unable to find containers in pod", "namespace", namespace, "podName", podName) } return podName, containers[0].Name, nil } @@ -91,7 +91,7 @@ func GetPodContainerFromStatefulSet(ctx context.Context, cli kubernetes.Interfac return podName, containerName, err } if len(pod) == 0 { - return podName, containerName, fmt.Errorf("Unable to find ready pod for statefulset %s/%s", namespace, ssName) + return podName, containerName, errkit.New("Unable to find ready pod for statefulset", "namespace", namespace, "statefulSet", ssName) } podName = pod[0].GetName() container, err := PodContainers(ctx, cli, namespace, podName) @@ -99,7 +99,7 @@ func GetPodContainerFromStatefulSet(ctx context.Context, cli kubernetes.Interfac return podName, containerName, err } if len(container) == 0 { - return podName, containerName, fmt.Errorf("Unable to find containers in pod %s/%s", namespace, podName) + return podName, containerName, errkit.New("Unable to find containers in pod", "namespace", namespace, "podName", podName) } return podName, container[0].Name, nil } diff --git a/pkg/kube/volume/volume.go b/pkg/kube/volume/volume.go index e1c99b9aa3..021f3cfceb 100644 --- a/pkg/kube/volume/volume.go +++ b/pkg/kube/volume/volume.go @@ -22,7 +22,7 @@ import ( "strings" "time" - "github.com/pkg/errors" + "github.com/kanisterio/errkit" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" @@ -66,7 +66,7 @@ func CreatePVC( size, err := resource.ParseQuantity(sizeFmt) emptyStorageClass := "" if err != nil { - return "", errors.Wrapf(err, "Unable to parse sizeFmt %s", sizeFmt) + return "", errkit.Wrap(err, "Unable to parse sizeFmt", "sizeFmt", sizeFmt) } if len(accessmodes) == 0 { accessmodes = []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce} @@ -104,7 +104,7 @@ func CreatePVC( if name != "" && apierrors.IsAlreadyExists(err) { return name, nil } - return "", errors.Wrapf(err, "Unable to create PVC %v", pvc) + return "", errkit.Wrap(err, "Unable to create PVC", "pvc", pvc) } return createdPVC.Name, nil } @@ -137,7 +137,7 @@ type CreatePVCFromSnapshotArgs struct { func CreatePVCFromSnapshot(ctx context.Context, args *CreatePVCFromSnapshotArgs) (string, error) { storageSize, err := getPVCRestoreSize(ctx, args) if err != nil { - return "", errors.Wrap(err, "Failed to get PVC restore size") + return "", errkit.Wrap(err, "Failed to get PVC restore size") } if len(args.AccessModes) == 0 { @@ -188,7 +188,7 @@ func CreatePVCFromSnapshot(ctx context.Context, args *CreatePVCFromSnapshotArgs) if args.VolumeName != "" && apierrors.IsAlreadyExists(err) { return args.VolumeName, nil } - return "", errors.Wrapf(err, "Unable to create PVC, PVC: %v", pvc) + return "", errkit.Wrap(err, "Unable to create PVC", "pvc", pvc) } return pvc.Name, err } @@ -199,25 +199,25 @@ func getPVCRestoreSize(ctx context.Context, args *CreatePVCFromSnapshotArgs) (*r if args.RestoreSize != "" { s, err := resource.ParseQuantity(args.RestoreSize) if err != nil { - return nil, fmt.Errorf("Failed to parse quantity (%s)", args.RestoreSize) + return nil, errkit.New("Failed to parse quantity", "restoreSize", args.RestoreSize) } quantities = append(quantities, &s) } sns, err := snapshot.NewSnapshotter(args.KubeCli, args.DynCli) if err != nil { - return nil, errors.Wrap(err, "Failed to get snapshotter") + return nil, errkit.Wrap(err, "Failed to get snapshotter") } snap, err := sns.Get(ctx, args.SnapshotName, args.Namespace) if err != nil { - return nil, errors.Wrap(err, "Failed to get snapshot") + return nil, errkit.Wrap(err, "Failed to get snapshot") } if snap.Status != nil && snap.Status.RestoreSize != nil { quantities = append(quantities, snap.Status.RestoreSize) } if len(quantities) == 0 { - return nil, fmt.Errorf("Restore size is empty and no restore size argument given, Volumesnapshot: %s", args.SnapshotName) + return nil, errkit.New("Restore size is empty and no restore size argument given", "volumeSnapshot", args.SnapshotName) } quantity := quantities[0] @@ -243,7 +243,7 @@ func CreatePV( sizeFmt := fmt.Sprintf("%d", vol.SizeInBytes) size, err := resource.ParseQuantity(sizeFmt) if err != nil { - return "", errors.Wrapf(err, "Unable to parse sizeFmt %s", sizeFmt) + return "", errkit.Wrap(err, "Unable to parse sizeFmt", "sizeFmt", sizeFmt) } matchLabels := map[string]string{pvMatchLabelName: filepath.Base(vol.ID)} @@ -289,12 +289,12 @@ func CreatePV( pv.ObjectMeta.Labels[kube.TopologyRegionLabelName] = zoneToRegion(vol.Az) default: - return "", errors.Errorf("Volume type %v(%T) not supported ", volType, volType) + return "", errkit.New("Volume type not supported", "volumeType", volType, "type", fmt.Sprintf("%T", volType)) } createdPV, err := kubeCli.CoreV1().PersistentVolumes().Create(ctx, &pv, metav1.CreateOptions{}) if err != nil { - return "", errors.Wrapf(err, "Unable to create PV for volume %v", pv) + return "", errkit.Wrap(err, "Unable to create PV for volume", "pv", pv) } return createdPV.Name, nil } diff --git a/pkg/kube/workload.go b/pkg/kube/workload.go index 8ead5548fd..0d492f371d 100644 --- a/pkg/kube/workload.go +++ b/pkg/kube/workload.go @@ -20,7 +20,7 @@ import ( "regexp" "strconv" - "github.com/pkg/errors" + "github.com/kanisterio/errkit" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -78,7 +78,7 @@ func CreateStatefulSet(ctx context.Context, cli kubernetes.Interface, namespace func StatefulSetReady(ctx context.Context, kubeCli kubernetes.Interface, namespace string, name string) (bool, string, error) { ss, err := kubeCli.AppsV1().StatefulSets(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { - return false, "", errors.Wrapf(err, "could not get StatefulSet{Namespace: %s, Name: %s}", namespace, name) + return false, "", errkit.Wrap(err, "could not get StatefulSet", "namespace", namespace, "name", name) } if ss.Status.ReadyReplicas != *ss.Spec.Replicas { status := fmt.Sprintf( @@ -103,7 +103,7 @@ func StatefulSetReady(ctx context.Context, kubeCli kubernetes.Interface, namespa func StatefulSetPods(ctx context.Context, kubeCli kubernetes.Interface, namespace string, name string) ([]corev1.Pod, []corev1.Pod, error) { ss, err := kubeCli.AppsV1().StatefulSets(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { - return nil, nil, errors.Wrapf(err, "could not get StatefulSet{Namespace: %s, Name: %s}", namespace, name) + return nil, nil, errkit.Wrap(err, "could not get StatefulSet", "namespace", namespace, "name", name) } return FetchPods(kubeCli, namespace, ss.GetUID()) } @@ -116,13 +116,13 @@ func WaitOnStatefulSetReady(ctx context.Context, kubeCli kubernetes.Interface, n if s != "" { status = s } - if apierrors.IsNotFound(errors.Cause(err)) { + if apierrors.IsNotFound(errkit.Unwrap(err)) { return false, nil } return ok, err }) if err != nil && status != "" { - return errors.Wrap(err, status) + return errkit.Wrap(err, status) } return err } @@ -131,7 +131,7 @@ func WaitOnStatefulSetReady(ctx context.Context, kubeCli kubernetes.Interface, n func DeploymentConfigReady(ctx context.Context, osCli osversioned.Interface, cli kubernetes.Interface, namespace, name string) (bool, error) { depConfig, err := osCli.AppsV1().DeploymentConfigs(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { - return false, errors.Wrapf(err, "could not get DeploymentConfig{Namespace: %s, Name: %s}", namespace, name) + return false, errkit.Wrap(err, "could not get DeploymentConfig", "namespace", namespace, "name", name) } if deploymentConfigComplete := depConfig.Status.UpdatedReplicas == depConfig.Spec.Replicas && @@ -174,7 +174,7 @@ func DeploymentConfigReady(ctx context.Context, osCli osversioned.Interface, cli func DeploymentReady(ctx context.Context, kubeCli kubernetes.Interface, namespace string, name string) (bool, string, error) { d, err := kubeCli.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { - return false, "", errors.Wrapf(err, "could not get Deployment{Namespace: %s, Name: %s}", namespace, name) + return false, "", errkit.Wrap(err, "could not get Deployment", "namespace", namespace, "name", name) } // Wait for deployment to complete. The deployment controller will check the downstream @@ -232,7 +232,7 @@ func DeploymentReady(ctx context.Context, kubeCli kubernetes.Interface, namespac func DeploymentConfigPods(ctx context.Context, osCli osversioned.Interface, kubeCli kubernetes.Interface, namespace, name string) ([]corev1.Pod, []corev1.Pod, error) { depConf, err := osCli.AppsV1().DeploymentConfigs(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { - return nil, nil, errors.Wrapf(err, "could not get DeploymentConfig{Namespace: %s, Name: %s}", namespace, name) + return nil, nil, errkit.Wrap(err, "could not get DeploymentConfig", "namespace", namespace, "name", name) } rc, err := FetchReplicationController(kubeCli, namespace, depConf.GetUID(), strconv.FormatInt(depConf.Status.LatestVersion, 10)) if err != nil { @@ -246,7 +246,7 @@ func DeploymentConfigPods(ctx context.Context, osCli osversioned.Interface, kube func DeploymentPods(ctx context.Context, kubeCli kubernetes.Interface, namespace string, name string) ([]corev1.Pod, []corev1.Pod, error) { d, err := kubeCli.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { - return nil, nil, errors.Wrapf(err, "could not get Deployment{Namespace: %s, Name: %s}", namespace, name) + return nil, nil, errkit.Wrap(err, "could not get Deployment", "namespace", namespace, "name", name) } rs, err := FetchReplicaSet(kubeCli, namespace, d.GetUID(), d.Annotations[RevisionAnnotation]) if err != nil { @@ -263,13 +263,13 @@ func WaitOnDeploymentReady(ctx context.Context, kubeCli kubernetes.Interface, na if s != "" { status = s } - if apierrors.IsNotFound(errors.Cause(err)) { + if apierrors.IsNotFound(errkit.Unwrap(err)) { return false, nil } return ok, err }) if err != nil && status != "" { - return errors.Wrap(err, status) + return errkit.Wrap(err, status) } return err } @@ -278,7 +278,7 @@ func WaitOnDeploymentReady(ctx context.Context, kubeCli kubernetes.Interface, na func WaitOnDeploymentConfigReady(ctx context.Context, osCli osversioned.Interface, kubeCli kubernetes.Interface, namespace, name string) error { return poll.Wait(ctx, func(ctx context.Context) (bool, error) { ok, err := DeploymentConfigReady(ctx, osCli, kubeCli, namespace, name) - if apierrors.IsNotFound(errors.Cause(err)) { + if apierrors.IsNotFound(errkit.Unwrap(err)) { return false, nil } return ok, err @@ -289,7 +289,7 @@ func WaitOnDeploymentConfigReady(ctx context.Context, osCli osversioned.Interfac func FetchReplicationController(cli kubernetes.Interface, namespace string, uid types.UID, revision string) (*corev1.ReplicationController, error) { repCtrls, err := cli.CoreV1().ReplicationControllers(namespace).List(context.TODO(), metav1.ListOptions{}) if err != nil { - return nil, errors.Wrapf(err, "Could not list ReplicationControllers") + return nil, errkit.Wrap(err, "Could not list ReplicationControllers") } for _, rc := range repCtrls.Items { @@ -310,13 +310,13 @@ func FetchReplicationController(cli kubernetes.Interface, namespace string, uid return nil, nil } -var errNotFound = fmt.Errorf("not found") +var errNotFound = errkit.NewSentinelErr("not found") // FetchReplicaSet fetches the replicaset matching the specified owner UID func FetchReplicaSet(cli kubernetes.Interface, namespace string, uid types.UID, revision string) (*appsv1.ReplicaSet, error) { rss, err := cli.AppsV1().ReplicaSets(namespace).List(context.TODO(), metav1.ListOptions{}) if err != nil { - return nil, errors.Wrap(err, "Could not list ReplicaSets") + return nil, errkit.Wrap(err, "Could not list ReplicaSets") } for _, rs := range rss.Items { // We ignore ReplicaSets without a single owner. @@ -333,7 +333,7 @@ func FetchReplicaSet(cli kubernetes.Interface, namespace string, uid types.UID, } return &rs, nil } - return nil, errors.Wrap(errNotFound, "Could not find a ReplicaSet for Deployment") + return nil, errkit.Wrap(errNotFound, "Could not find a ReplicaSet for Deployment") } // FetchPods fetches the pods matching the specified owner UID and splits them @@ -341,7 +341,7 @@ func FetchReplicaSet(cli kubernetes.Interface, namespace string, uid types.UID, func FetchPods(cli kubernetes.Interface, namespace string, uid types.UID) (runningPods []corev1.Pod, notRunningPods []corev1.Pod, err error) { pods, err := cli.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{}) if err != nil { - return nil, nil, errors.Wrap(err, "Could not list Pods") + return nil, nil, errkit.Wrap(err, "Could not list Pods") } for _, pod := range pods.Items { if len(pod.OwnerReferences) != 1 || @@ -360,12 +360,12 @@ func FetchPods(cli kubernetes.Interface, namespace string, uid types.UID) (runni func ScaleStatefulSet(ctx context.Context, kubeCli kubernetes.Interface, namespace string, name string, replicas int32, waitForReady bool) error { ss, err := kubeCli.AppsV1().StatefulSets(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { - return errors.Wrapf(err, "Could not get Statefulset{Namespace %s, Name: %s}", namespace, name) + return errkit.Wrap(err, "Could not get Statefulset", "namespace", namespace, "name", name) } ss.Spec.Replicas = &replicas _, err = kubeCli.AppsV1().StatefulSets(namespace).Update(ctx, ss, metav1.UpdateOptions{}) if err != nil { - return errors.Wrapf(err, "Could not update Statefulset{Namespace %s, Name: %s}", namespace, name) + return errkit.Wrap(err, "Could not update Statefulset", "namespace", namespace, "name", name) } if !waitForReady { return nil @@ -376,12 +376,12 @@ func ScaleStatefulSet(ctx context.Context, kubeCli kubernetes.Interface, namespa func ScaleDeployment(ctx context.Context, kubeCli kubernetes.Interface, namespace string, name string, replicas int32, waitForReady bool) error { d, err := kubeCli.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { - return errors.Wrapf(err, "Could not get Deployment{Namespace %s, Name: %s}", namespace, name) + return errkit.Wrap(err, "Could not get Deployment", "namespace", namespace, "name", name) } d.Spec.Replicas = &replicas _, err = kubeCli.AppsV1().Deployments(namespace).Update(ctx, d, metav1.UpdateOptions{}) if err != nil { - return errors.Wrapf(err, "Could not update Deployment{Namespace %s, Name: %s}", namespace, name) + return errkit.Wrap(err, "Could not update Deployment", "namespace", namespace, "name", name) } if !waitForReady { return nil @@ -392,12 +392,12 @@ func ScaleDeployment(ctx context.Context, kubeCli kubernetes.Interface, namespac func ScaleDeploymentConfig(ctx context.Context, kubeCli kubernetes.Interface, osCli osversioned.Interface, namespace string, name string, replicas int32, waitForReady bool) error { dc, err := osCli.AppsV1().DeploymentConfigs(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { - return errors.Wrapf(err, "Could not get DeploymentConfig{Namespace %s, Name: %s}", namespace, name) + return errkit.Wrap(err, "Could not get DeploymentConfig", "namespace", namespace, "name", name) } dc.Spec.Replicas = replicas _, err = osCli.AppsV1().DeploymentConfigs(namespace).Update(ctx, dc, metav1.UpdateOptions{}) if err != nil { - return errors.Wrapf(err, "Could not update DeploymentConfig{Namespace %s, Name: %s}", namespace, name) + return errkit.Wrap(err, "Could not update DeploymentConfig", "namespace", namespace, "name", name) } if !waitForReady { return nil @@ -422,7 +422,7 @@ func DeploymentVolumes(cli kubernetes.Interface, d *appsv1.Deployment) (volNameT func PodContainers(ctx context.Context, kubeCli kubernetes.Interface, namespace string, name string) ([]corev1.Container, error) { p, err := kubeCli.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { - return nil, errors.Wrapf(err, "could not get Pod{Namespace: %s, Name: %s}", namespace, name) + return nil, errkit.Wrap(err, "could not get Pod", "namespace", namespace, "name", name) } return p.Spec.Containers, nil } @@ -501,14 +501,14 @@ func IsPodRunning(cli kubernetes.Interface, podName, podNamespace string) (bool, } if len(pod.Status.ContainerStatuses) == 0 { - return false, errors.New(fmt.Sprintf("Could not find ready pod. Name:%s , Namespace:%s", podName, podNamespace)) + return false, errkit.New("Could not find ready pod.", "Name", podName, "Namespace", podNamespace) } // loop through the all the container statuses of this pod // and fail fast if any container is not ready for i, v := range pod.Status.ContainerStatuses { if !v.Ready { - return false, errors.New(fmt.Sprintf("Container at position %d is not running.", i)) + return false, errkit.New("Container is not running.", "containerPosition", i) } } @@ -518,7 +518,7 @@ func IsPodRunning(cli kubernetes.Interface, podName, podNamespace string) (bool, func StatefulSetReplicas(ctx context.Context, kubeCli kubernetes.Interface, namespace, name string) (int32, error) { sts, err := kubeCli.AppsV1().StatefulSets(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { - return 0, errors.Wrapf(err, "Could not get StatefulSet{Namespace %s, Name: %s}, to figure out replicas", namespace, name) + return 0, errkit.Wrap(err, "Could not get StatefulSet, to figure out replicas", "Namespace", namespace, "StatefulSet", name) } return *sts.Spec.Replicas, nil } @@ -526,7 +526,7 @@ func StatefulSetReplicas(ctx context.Context, kubeCli kubernetes.Interface, name func DeploymentReplicas(ctx context.Context, kubeCli kubernetes.Interface, namespace, name string) (int32, error) { d, err := kubeCli.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { - return 0, errors.Wrapf(err, "Could not get Deployment{Namespace %s, Name: %s}, to figure out replicas", namespace, name) + return 0, errkit.Wrap(err, "Could not get Deployment, to figure out replicas", "Namespace", namespace, "Deployment", name) } return *d.Spec.Replicas, nil } @@ -534,7 +534,7 @@ func DeploymentReplicas(ctx context.Context, kubeCli kubernetes.Interface, names func DeploymentConfigReplicas(ctx context.Context, osCli osversioned.Interface, namespace, name string) (int32, error) { dc, err := osCli.AppsV1().DeploymentConfigs(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { - return 0, errors.Wrapf(err, "Could not get DeploymentConfig{Namespace %s, Name: %s}, to figure out replicas", namespace, name) + return 0, errkit.Wrap(err, "Could not get DeploymentConfig, to figure out replicas", "Namespace", namespace, "DeploymentConfig", name) } return dc.Spec.Replicas, nil } diff --git a/pkg/kube/workload_ready_test.go b/pkg/kube/workload_ready_test.go index e0f8d41e8f..7f392342a1 100644 --- a/pkg/kube/workload_ready_test.go +++ b/pkg/kube/workload_ready_test.go @@ -4,6 +4,7 @@ import ( "context" "time" + "github.com/kanisterio/kanister/pkg/errorchecker" . "gopkg.in/check.v1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -53,7 +54,7 @@ func (s *WorkloadReadySuite) TestWaitOnStatefulSetReady(c *C) { defer cancel() err := WaitOnStatefulSetReady(ctx, getCli(tc.input), tc.input.namespace, tc.input.name) if tc.want != "" { - c.Assert(err, ErrorMatches, tc.want) + errorchecker.AssertErrorMessage(c, err, tc.want) } else { c.Assert(err, IsNil) } @@ -99,7 +100,7 @@ func (s *WorkloadReadySuite) TestWaitOnDeploymentReady(c *C) { defer cancel() err := WaitOnDeploymentReady(ctx, getCli(tc.input), tc.input.namespace, tc.input.name) if tc.want != "" { - c.Assert(err, ErrorMatches, tc.want) + errorchecker.AssertErrorMessage(c, err, tc.want) } else { c.Assert(err, IsNil) } diff --git a/releasenotes/README.md b/releasenotes/README.md new file mode 100644 index 0000000000..fc5389c99f --- /dev/null +++ b/releasenotes/README.md @@ -0,0 +1,41 @@ +## Release notes + +Release notes are generated using [https://docs.openstack.org/reno/latest/](reno) and are stored in `releasenotes/notes` directory. + +Reno allows to generate release notes using files in the repository as opposed to generating from commit messages. +This makes it easier to review and edit release notes. + +## Development flow + +When submitting a PR with some changes worthy of mentioning in the notes (new feature, bugfix, deprecation, update requirements), +committer should add a new note file using `reno new --edit` or `make reno-new note=`. + +New file will be created in `releasenotes/notes` directory with default template. +Summary of the change should be added to this file to reflect the change and additional information such as deprecations or upgrade requirements. +It's recommended to remove unused template fields. + +When reviewing a PR, a reviewer should check if there are release notes added if necessary and either request or add a new note if they have push access to the branch. + +## Generating changelogs + +Changelogs would be generated for each release by maintainers as a part of the release process. + +Changelog can be generated using: + +``` +make reno-report +``` +or to mark specific current version: +``` +make reno-report VERSION=$CURRENT_VERSION +``` + +This will create a CHANGELOG.md and CHANGELOG_CURRENT.md files with changes from committed release notes. +CHANGELOG_CURRENT.md will only contain changes for the current version. + +Changelog file can be specified when running goreleaser build using make (will use `CHANGELOG_CURRENT.md` if not specified): + +``` +make gorelease CHANGELOG_FILE=./CHANGELOG_CURRENT.md +``` + diff --git a/reno.yaml b/reno.yaml new file mode 100644 index 0000000000..f989cfba2b --- /dev/null +++ b/reno.yaml @@ -0,0 +1,29 @@ +--- +collapse_pre_releases: false +earliest_version: 0.104.0 +add_release_date: false +stop_at_branch_base: true +ignore_notes: [] + +sections: + - [features, New Features] + - [fixes, Bug Fixes] + - [security, Security Issues] + - [issues, Known Issues] + - [upgrade, Upgrade Notes] + - [deprecations, Deprecations] + - [other, Other Notes] + +unreleased_version_title: '' + +prelude_section_name: release_summary +template: | + --- + release_summary: + features: + issues: + upgrade: + deprecations: + security: + fixes: + other: