├── .github └── workflows │ └── pipeline.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── assets ├── kubebackup-logo-wide.png ├── kubebackup-logo.png └── kubebackup-logo.svg ├── go.mod ├── go.sum ├── helm └── kubebackup │ ├── Chart.yaml │ ├── Chart.yaml.template │ ├── OWNERS │ ├── README.md │ ├── app-readme.md │ ├── questions.yml │ ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── clusterrole.yaml │ ├── clusterrolebinding.yaml │ ├── deployment.yaml │ ├── secret.yaml │ └── serviceaccount.yaml │ ├── values.yaml │ └── values.yaml.template ├── main.go ├── makefile └── pkg ├── backup └── backup.go ├── config └── config.go ├── k8s └── k8s.go ├── logging └── logging.go ├── metrics └── metrics.go ├── s3 └── s3.go └── version └── version.go /.github/workflows/pipeline.yml: -------------------------------------------------------------------------------- 1 | name: Build, Test and Publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | Test: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v4 18 | with: 19 | go-version: "1.22" 20 | 21 | - name: Install static analysis tools 22 | run: | 23 | go install golang.org/x/lint/golint@latest 24 | go install honnef.co/go/tools/cmd/staticcheck@latest 25 | go install github.com/securego/gosec/v2/cmd/gosec@latest 26 | go install github.com/psampaz/go-mod-outdated@latest 27 | go install github.com/remyoudompheng/go-misc/deadcode@latest 28 | 29 | - name: Dependency management 30 | run: | 31 | go mod vendor 32 | go mod verify 33 | go mod tidy 34 | 35 | - name: Security scanning 36 | run: | 37 | gosec ./... 38 | 39 | Build: 40 | needs: Test 41 | runs-on: ubuntu-latest 42 | 43 | steps: 44 | - uses: actions/checkout@v4 45 | 46 | - name: Set up Docker Buildx 47 | uses: docker/setup-buildx-action@v3 48 | 49 | - name: Login to DockerHub 50 | uses: docker/login-action@v3 51 | with: 52 | username: ${{ secrets.DOCKERHUB_USERNAME }} 53 | password: ${{ secrets.DOCKERHUB_TOKEN }} 54 | 55 | - name: Docker build 56 | run: | 57 | docker buildx build \ 58 | --platform linux/amd64 \ 59 | --pull \ 60 | --build-arg VERSION=v${{ github.run_number }} \ 61 | --build-arg GIT_COMMIT=${{ github.sha }} \ 62 | --build-arg BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \ 63 | --cache-from cube8021/kubebackup:latest \ 64 | -t cube8021/kubebackup:"v${{ github.run_number }}" \ 65 | -t cube8021/kubebackup:latest \ 66 | --push \ 67 | -f Dockerfile . 68 | 69 | Publish: 70 | runs-on: ubuntu-latest 71 | needs: 72 | - Build 73 | 74 | steps: 75 | - name: Checkout repository 76 | uses: actions/checkout@v4 77 | 78 | - name: Set up Helm 79 | uses: azure/setup-helm@v4.2.0 80 | 81 | - name: Helm Lint 82 | run: helm lint charts/kubebackup/ 83 | 84 | - name: Set up Helm 85 | uses: azure/setup-helm@v1 86 | with: 87 | version: v3.7.1 88 | 89 | - name: Package Helm chart 90 | run: | 91 | export CHART_VERSION="v${{ github.run_number }}" 92 | export APP_VERSION="v${{ github.run_number }}" 93 | export IMAGE_TAG="v${{ github.run_number }}" 94 | echo "CHART_VERSION=${CHART_VERSION}" 95 | echo "APP_VERSION=${APP_VERSION}" 96 | envsubst < charts/kubebackup/Chart.yaml.template > charts/kubebackup/Chart.yaml 97 | envsubst < charts/kubebackup/values.yaml.template > charts/kubebackup/values.yaml 98 | helm package charts/kubebackup --destination helm/repo 99 | 100 | - name: Checkout helm-chart repository 101 | uses: actions/checkout@v4 102 | with: 103 | repository: supporttools/helm-chart 104 | path: helm-chart 105 | token: ${{ secrets.BOT_TOKEN }} 106 | 107 | - name: Configure Git 108 | run: | 109 | git config --global user.email "github-action@users.noreply.github.com" 110 | git config --global user.name "GitHub Action" 111 | 112 | - name: Update Helm repository 113 | run: | 114 | cp helm/repo/kubebackup-*.tgz helm-chart/ 115 | cd helm-chart 116 | helm repo index . --url https://charts.support.tools/ 117 | git add . 118 | git commit -m "Update Helm chart for kubebackup" 119 | git push 120 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | 23 | # kubebackup binary 24 | bin/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use golang alpine image as the builder stage 2 | FROM golang:1.22.4-alpine3.20 AS builder 3 | 4 | # Install git and other necessary tools 5 | RUN apk update && apk add --no-cache git bash 6 | 7 | # Set the Current Working Directory inside the container 8 | WORKDIR /src 9 | 10 | # Copy go.mod and go.sum files first to leverage Docker cache 11 | COPY go.mod go.sum ./ 12 | 13 | # Fetch dependencies 14 | RUN go mod download 15 | 16 | # Copy the rest of the application source code 17 | COPY . . 18 | 19 | # Build arguments for versioning 20 | ARG VERSION 21 | ARG GIT_COMMIT 22 | ARG BUILD_DATE 23 | 24 | # Build the Go app with static linking 25 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ 26 | go build -a -ldflags "-s -w \ 27 | -X github.com/mattmattox/kubebackup/pkg/version.Version=${VERSION} \ 28 | -X github.com/mattmattox/kubebackup/pkg/version.GitCommit=${GIT_COMMIT} \ 29 | -X github.com/mattmattox/kubebackup/pkg/version.BuildTime=${BUILD_DATE}" \ 30 | -o /kubebackup 31 | 32 | # Use a minimal base image 33 | FROM alpine:3.18 34 | 35 | # Install ca-certificates and other necessary tools 36 | RUN apk add --no-cache ca-certificates bash curl 37 | 38 | # Copy the statically compiled executable 39 | COPY --from=builder /kubebackup /kubebackup 40 | 41 | # Set the entrypoint 42 | ENTRYPOINT ["/kubebackup"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![KubeBackup Logo](https://github.com/mattmattox/kubebackup/raw/master/assets/kubebackup-logo.png) 2 | 3 | [![Build, Test and Publish](https://github.com/mattmattox/kubebackup/actions/workflows/build-and-publish.yml/badge.svg)](https://github.com/mattmattox/kubebackup/actions/workflows/build-and-publish.yml) 4 | 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/mattmattox/kubebackup)](https://goreportcard.com/report/github.com/mattmattox/kubebackup) 6 | 7 | [![Docker Pulls](https://img.shields.io/docker/pulls/cube8021/kubebackup.svg)](https://hub.docker.com/r/cube8021/kubebackup) 8 | 9 | [![GitHub release (latest by date)](https://img.shields.io/github/v/release/mattmattox/kubebackup)](https://github.com/mattmattox/kubebackup/releases) 10 | 11 | [![License](https://img.shields.io/github/license/mattmattox/kubebackup)](https://github.com/mattmattox/kubebackup/blob/master/LICENSE) 12 | 13 | # What is KubeBackup? 14 | 15 | KubeBackup is a tool for backing up the configuration files in a Kubernetes cluster and uploading them to a S3 bucket. 16 | 17 | ## How does KubeBackup work? 18 | 19 | KubeBackup accesses the Kubernetes API from inside a container. Inside that container, a script exports all the cluster and namespace yaml files. These files can be used to redeploy an environment. All the exported yaml files are compressed and uploaded to an S3 Bucket. 20 | 21 | ## Install 22 | 23 | ## Install / Upgrade 24 | ``` 25 | helm repo add SupportTools https://charts.support.tools 26 | helm repo update 27 | helm upgrade --install kubebackup SupportTools/kubebackup \ 28 | --set s3.region="us-east-2" \ 29 | --set s3.bucket="my-bucket" \ 30 | --set s3.folder="my-cluster" \ 31 | --set s3.accessKey="S3_ACCESS_KEY_GOES_HERE" \ 32 | --set s3.secretKey="S3_SECRET_KEY_GOES_HERE" 33 | ``` 34 | 35 | 36 | ## How it works 37 | KubeBackup is a helm chart that deploys a pod. This will take a YAML backup of your cluster and upload it to an S3 bucket. 38 | 39 | The script connects to the Kubernetes API using either the provided kubeconfig file or the in-cluster configuration, if available. It then retrieves the list of available API resources and iterates through them to fetch namespaced and cluster-scoped objects. 40 | 41 | Namespaced objects are grouped by namespace and saved in the `namespace-scoped//` directory, while cluster-scoped objects are saved in the `cluster-scoped/` directory. The output files are named .yaml. 42 | 43 | ## Configuration 44 | The following table lists the configurable parameters of the KubeBackup chart and their default values. 45 | 46 | ## Environment Variables 47 | 48 | | Environment Variable | Description | Default Value | 49 | |----------------------------|---------------------------------------------------------|---------------------| 50 | | `DEBUG` | Enable debug mode | `false` | 51 | | `LOG_LEVEL` | Logging level (e.g., `info`, `debug`) | `info` | 52 | | `KUBECONFIG` | Path to Kubernetes config | `~/.kube/config` | 53 | | `BACKUP_DIR` | Directory for storing backups temporarily | `/tmp` | 54 | | `BACKUP_INTERVAL` | Backup interval in seconds | `12` | 55 | | `RETENTION` | Retention period in days for backups in S3 | `30` | 56 | | `S3_BUCKET` | S3 bucket name | | 57 | | `S3_FOLDER` | Folder path within the S3 bucket | | 58 | | `S3_ACCESS_KEY_ID` | S3 access key | | 59 | | `S3_SECRET_ACCESS_KEY` | S3 secret key | | 60 | | `S3_REGION` | S3 region | | 61 | | `S3_ENDPOINT` | Custom S3 endpoint | | 62 | | `S3_DISABLE_SSL` | Disable SSL verification (`true` or `false`) | `false` | 63 | | `S3_CUSTOM_CA_PATH` | Path to custom CA certificate file | | 64 | | `METRICS_PORT` | Metrics server port | `9000` | 65 | 66 | 67 | ## Building the script from source 68 | To build the script from source, you will need to have the following installed: 69 | * [Go](https://golang.org/dl/) 70 | * [Docker](https://www.docker.com/get-started) 71 | 72 | To build the script, run the following commands: 73 | ``` 74 | git clone 75 | cd kubebackup 76 | make build 77 | ``` 78 | 79 | ## Contributing 80 | If you would like to contribute to this project, please fork the repo and submit a pull request. 81 | 82 | ## License 83 | This project is licensed under the Apache License - see the [LICENSE](LICENSE) file for details. -------------------------------------------------------------------------------- /assets/kubebackup-logo-wide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattmattox/kubebackup/d44934f9eac9341ee1a88409c3efe2ee2410a6de/assets/kubebackup-logo-wide.png -------------------------------------------------------------------------------- /assets/kubebackup-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattmattox/kubebackup/d44934f9eac9341ee1a88409c3efe2ee2410a6de/assets/kubebackup-logo.png -------------------------------------------------------------------------------- /assets/kubebackup-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mattmattox/kubebackup 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go v1.44.234 7 | github.com/prometheus/client_golang v1.14.0 8 | github.com/sirupsen/logrus v1.9.0 9 | k8s.io/apimachinery v0.26.3 10 | k8s.io/client-go v0.26.3 11 | sigs.k8s.io/yaml v1.3.0 12 | ) 13 | 14 | require ( 15 | github.com/beorn7/perks v1.0.1 // indirect 16 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/dustin/go-humanize v1.0.1 // indirect 19 | github.com/emicklei/go-restful/v3 v3.9.0 // indirect 20 | github.com/evanphx/json-patch v4.12.0+incompatible // indirect 21 | github.com/go-logr/logr v1.2.3 // indirect 22 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 23 | github.com/go-openapi/jsonreference v0.20.0 // indirect 24 | github.com/go-openapi/swag v0.19.14 // indirect 25 | github.com/gogo/protobuf v1.3.2 // indirect 26 | github.com/golang/protobuf v1.5.2 // indirect 27 | github.com/google/gnostic v0.5.7-v3refs // indirect 28 | github.com/google/go-cmp v0.5.9 // indirect 29 | github.com/google/gofuzz v1.1.0 // indirect 30 | github.com/google/uuid v1.3.0 // indirect 31 | github.com/imdario/mergo v0.3.6 // indirect 32 | github.com/jmespath/go-jmespath v0.4.0 // indirect 33 | github.com/josharian/intern v1.0.0 // indirect 34 | github.com/json-iterator/go v1.1.12 // indirect 35 | github.com/klauspost/compress v1.16.0 // indirect 36 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 37 | github.com/mailru/easyjson v0.7.6 // indirect 38 | github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect 39 | github.com/minio/md5-simd v1.1.2 // indirect 40 | github.com/minio/minio-go/v7 v7.0.50 // indirect 41 | github.com/minio/sha256-simd v1.0.0 // indirect 42 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 43 | github.com/modern-go/reflect2 v1.0.2 // indirect 44 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 45 | github.com/pkg/errors v0.9.1 // indirect 46 | github.com/prometheus/client_model v0.3.0 // indirect 47 | github.com/prometheus/common v0.37.0 // indirect 48 | github.com/prometheus/procfs v0.8.0 // indirect 49 | github.com/robfig/cron/v3 v3.0.1 // indirect 50 | github.com/rs/xid v1.4.0 // indirect 51 | github.com/spf13/pflag v1.0.5 // indirect 52 | golang.org/x/crypto v0.6.0 // indirect 53 | golang.org/x/net v0.7.0 // indirect 54 | golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect 55 | golang.org/x/sync v0.9.0 // indirect 56 | golang.org/x/sys v0.5.0 // indirect 57 | golang.org/x/term v0.5.0 // indirect 58 | golang.org/x/text v0.7.0 // indirect 59 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect 60 | google.golang.org/appengine v1.6.7 // indirect 61 | google.golang.org/protobuf v1.28.1 // indirect 62 | gopkg.in/inf.v0 v0.9.1 // indirect 63 | gopkg.in/ini.v1 v1.67.0 // indirect 64 | gopkg.in/yaml.v2 v2.4.0 // indirect 65 | gopkg.in/yaml.v3 v3.0.1 // indirect 66 | k8s.io/api v0.26.3 // indirect 67 | k8s.io/klog/v2 v2.80.1 // indirect 68 | k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect 69 | k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect 70 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 71 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 72 | ) 73 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 16 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 17 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 18 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 19 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 20 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 21 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 22 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 23 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 24 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 25 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 26 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 27 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 28 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 29 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 30 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 31 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 32 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 33 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 34 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 35 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 36 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 37 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 38 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 39 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 40 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= 41 | github.com/aws/aws-sdk-go v1.44.234 h1:8YbQ5AhpgV/cC7jYX8qS34Am/vcn2ZoIFJ1qIgwOL+0= 42 | github.com/aws/aws-sdk-go v1.44.234/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= 43 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 44 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 45 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 46 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 47 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 48 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 49 | github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= 50 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 51 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 52 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 53 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 54 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 55 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 56 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 57 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 58 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 59 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 60 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 61 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 62 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 63 | github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= 64 | github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 65 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 66 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 67 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 68 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 69 | github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= 70 | github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 71 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 72 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 73 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 74 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 75 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 76 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 77 | github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= 78 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 79 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 80 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 81 | github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= 82 | github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 83 | github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= 84 | github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 85 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 86 | github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= 87 | github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 88 | github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= 89 | github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= 90 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 91 | github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= 92 | github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= 93 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 94 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 95 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 96 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 97 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 98 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 99 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 100 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 101 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 102 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 103 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 104 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 105 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 106 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 107 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 108 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 109 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 110 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 111 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 112 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 113 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 114 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 115 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 116 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 117 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 118 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 119 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 120 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 121 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 122 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 123 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 124 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 125 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 126 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 127 | github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= 128 | github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= 129 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 130 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 131 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 132 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 133 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 134 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 135 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 136 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 137 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 138 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 139 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 140 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 141 | github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= 142 | github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 143 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 144 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 145 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 146 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 147 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 148 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 149 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 150 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 151 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 152 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 153 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 154 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 155 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 156 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 157 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 158 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 159 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 160 | github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= 161 | github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 162 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 163 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 164 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 165 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 166 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 167 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 168 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 169 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 170 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 171 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 172 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 173 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 174 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 175 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 176 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 177 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 178 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 179 | github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= 180 | github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 181 | github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 182 | github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 183 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= 184 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 185 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 186 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 187 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 188 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 189 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 190 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 191 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 192 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 193 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 194 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 195 | github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= 196 | github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 197 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 198 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 199 | github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= 200 | github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= 201 | github.com/minio/minio-go/v7 v7.0.50 h1:4IL4V8m/kI90ZL6GupCARZVrBv8/XrcKcJhaJ3iz68k= 202 | github.com/minio/minio-go/v7 v7.0.50/go.mod h1:IbbodHyjUAguneyucUaahv+VMNs/EOTV9du7A7/Z3HU= 203 | github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= 204 | github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= 205 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 206 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 207 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 208 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 209 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 210 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 211 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 212 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 213 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 214 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 215 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 216 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 217 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 218 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 219 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 220 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 221 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 222 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 223 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 224 | github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= 225 | github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= 226 | github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= 227 | github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= 228 | github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= 229 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 230 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 231 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 232 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 233 | github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= 234 | github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= 235 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 236 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= 237 | github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= 238 | github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= 239 | github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= 240 | github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= 241 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 242 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 243 | github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= 244 | github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 245 | github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 246 | github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= 247 | github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= 248 | github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= 249 | github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= 250 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 251 | github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= 252 | github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 253 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 254 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 255 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 256 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 257 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 258 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 259 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 260 | github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 261 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 262 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 263 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 264 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 265 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 266 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 267 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 268 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 269 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 270 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 271 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 272 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 273 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 274 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 275 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 276 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 277 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 278 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 279 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 280 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 281 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 282 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 283 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 284 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 285 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 286 | golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= 287 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= 288 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 289 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 290 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 291 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 292 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 293 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 294 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 295 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 296 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 297 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 298 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 299 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 300 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 301 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 302 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 303 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 304 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 305 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 306 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 307 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 308 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 309 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 310 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 311 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 312 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 313 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 314 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 315 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 316 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 317 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 318 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 319 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 320 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 321 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 322 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 323 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 324 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 325 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 326 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 327 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 328 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 329 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 330 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 331 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 332 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 333 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 334 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 335 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 336 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 337 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 338 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 339 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 340 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 341 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 342 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 343 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 344 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 345 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 346 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 347 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 348 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 349 | golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 350 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 351 | golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 352 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 353 | golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= 354 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 355 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 356 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 357 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 358 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 359 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 360 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 361 | golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 362 | golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg= 363 | golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= 364 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 365 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 366 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 367 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 368 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 369 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 370 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 371 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 372 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 373 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 374 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 375 | golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= 376 | golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 377 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 378 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 379 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 380 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 381 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 382 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 383 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 384 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 385 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 386 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 387 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 388 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 389 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 390 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 391 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 392 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 393 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 394 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 395 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 396 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 397 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 398 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 399 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 400 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 401 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 402 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 403 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 404 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 405 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 406 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 407 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 408 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 409 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 410 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 411 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 412 | golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 413 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 414 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 415 | golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 416 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 417 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 418 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 419 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 420 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 421 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 422 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 423 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 424 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 425 | golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 426 | golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= 427 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 428 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 429 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 430 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 431 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 432 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 433 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 434 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 435 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 436 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 437 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 438 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 439 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 440 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 441 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= 442 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 443 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 444 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 445 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 446 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 447 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 448 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 449 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 450 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 451 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 452 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 453 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 454 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 455 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 456 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 457 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 458 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 459 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 460 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 461 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 462 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 463 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 464 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 465 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 466 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 467 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 468 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 469 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 470 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 471 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 472 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 473 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 474 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 475 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 476 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 477 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 478 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 479 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 480 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 481 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 482 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 483 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 484 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 485 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 486 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 487 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 488 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 489 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 490 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 491 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 492 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 493 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 494 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 495 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 496 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 497 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 498 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 499 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 500 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 501 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 502 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 503 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 504 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 505 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 506 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 507 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 508 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 509 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 510 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 511 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 512 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 513 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 514 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 515 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 516 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 517 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 518 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 519 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 520 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 521 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 522 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 523 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 524 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 525 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 526 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 527 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 528 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 529 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 530 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 531 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 532 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 533 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 534 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 535 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 536 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 537 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 538 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 539 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 540 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 541 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 542 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 543 | google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 544 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 545 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 546 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 547 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 548 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 549 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 550 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 551 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 552 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 553 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 554 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 555 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 556 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 557 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 558 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 559 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 560 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 561 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 562 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 563 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 564 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 565 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 566 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 567 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 568 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 569 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 570 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 571 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 572 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 573 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 574 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 575 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 576 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 577 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 578 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 579 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 580 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 581 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 582 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 583 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 584 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 585 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 586 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 587 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 588 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 589 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 590 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 591 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 592 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 593 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 594 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 595 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 596 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 597 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 598 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 599 | k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU= 600 | k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE= 601 | k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k= 602 | k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= 603 | k8s.io/client-go v0.26.3 h1:k1UY+KXfkxV2ScEL3gilKcF7761xkYsSD6BC9szIu8s= 604 | k8s.io/client-go v0.26.3/go.mod h1:ZPNu9lm8/dbRIPAgteN30RSXea6vrCpFvq+MateTUuQ= 605 | k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= 606 | k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= 607 | k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= 608 | k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= 609 | k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs= 610 | k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 611 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 612 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 613 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 614 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= 615 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 616 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= 617 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= 618 | sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= 619 | sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= 620 | -------------------------------------------------------------------------------- /helm/kubebackup/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | name: kubebackup 3 | version: v0.1.0 4 | appVersion: v0.1.0 5 | description: KubeBackup is a tool for backing up the configuration files in a Kubernetes cluster and uploading them to a S3 bucket. 6 | keywords: 7 | - kubernetes 8 | - backup 9 | home: https://github.com/mattmattox/kubebackup 10 | sources: 11 | - https://github.com/mattmattox/kubebackup 12 | maintainers: 13 | - name: mattmattox 14 | email: mmattox@support.tools 15 | icon: https://github.com/mattmattox/kubebackup/raw/master/assets/kubebackup-logo.png 16 | -------------------------------------------------------------------------------- /helm/kubebackup/Chart.yaml.template: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | name: kubebackup 3 | version: ${CHART_VERSION} 4 | appVersion: ${APP_VERSION} 5 | description: KubeBackup is a tool for backing up the configuration files in a Kubernetes cluster and uploading them to a S3 bucket. 6 | keywords: 7 | - kubernetes 8 | - backup 9 | home: https://github.com/mattmattox/kubebackup 10 | sources: 11 | - https://github.com/mattmattox/kubebackup 12 | maintainers: 13 | - name: mattmattox 14 | email: mmattox@support.tools 15 | icon: https://github.com/mattmattox/kubebackup/raw/master/assets/kubebackup-logo.png 16 | -------------------------------------------------------------------------------- /helm/kubebackup/OWNERS: -------------------------------------------------------------------------------- 1 | approvers: 2 | - mattmattox 3 | reviewers: 4 | - mattmattox 5 | -------------------------------------------------------------------------------- /helm/kubebackup/README.md: -------------------------------------------------------------------------------- 1 | # kubebackup 2 | 3 | [KubeBackup](https://github.com/mattmattox/kubebackup) is a tool for backing up the configuration files in a Kubernetes cluster and uploading them to a S3 bucket. 4 | 5 | ## How does KubeBackup work? 6 | KubeBackup accessing the kubernetes API from inside a containter. Inside that containter there is a script will export all the cluster and namespace yaml files. These files can be used to redeploy an environment. All the exported yaml files are compressed and uploaded to an S3 Bucket. 7 | -------------------------------------------------------------------------------- /helm/kubebackup/app-readme.md: -------------------------------------------------------------------------------- 1 | # KubeBackup 2 | 3 | [KubeBackup](https://github.com/mattmattox/kubebackup) is a tool for backing up the configuration files in a Kubernetes cluster and uploading them to a S3 bucket. 4 | 5 | ## How does KubeBackup work? 6 | KubeBackup accessing the kubernetes API from inside a containter. Inside that containter there is a script will export all the cluster and namespace yaml files. These files can be used to redeploy an environment. All the exported yaml files are compressed and uploaded to an S3 Bucket. 7 | -------------------------------------------------------------------------------- /helm/kubebackup/questions.yml: -------------------------------------------------------------------------------- 1 | labels: 2 | io.cattle.role: cluster # options are cluster/project 3 | categories: 4 | - kubebackup 5 | namespace: kube-system 6 | questions: 7 | - variable: defaultImage 8 | default: true 9 | description: "Use default Docker image" 10 | label: Use Default Image 11 | type: boolean 12 | show_subquestion_if: false 13 | group: "Container Images" 14 | subquestions: 15 | - variable: image.repository 16 | default: "docker.io/cube8021/kubebackup" 17 | description: "Docker image repository" 18 | type: string 19 | label: Image Repository 20 | - variable: image.tag 21 | default: "v0.1.12" 22 | description: "Docker image tag" 23 | type: string 24 | label: Image Tag 25 | - variable: CRON_SCHEDULE 26 | required: true 27 | default: '00 00 * * *' 28 | description: "Backup schedule in crontab format" 29 | type: string 30 | label: "Backup schedule" 31 | - variable: s3.region 32 | default: "us-east-1" 33 | description: "S3 region" 34 | type: string 35 | label: S3 Region 36 | required: true 37 | group: "Storage System Settings" 38 | - variable: s3.bucket 39 | default: "" 40 | description: "S3 bucket name" 41 | type: string 42 | label: S3 Bucket Name 43 | required: true 44 | group: "Storage System Settings" 45 | - variable: s3.accessKey 46 | default: "" 47 | description: "Access Key for S3 configuration " 48 | type: string 49 | label: AWS S3 AccessKey 50 | required: true 51 | group: "Storage System Settings" 52 | - variable: s3.secretKey 53 | default: "" 54 | description: "Secret Key for S3 configuration " 55 | type: password 56 | label: AWS S3 SecretKey 57 | required: true 58 | group: "Storage System Settings" 59 | -------------------------------------------------------------------------------- /helm/kubebackup/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | ********************************************************************************* 2 | *** PLEASE BE PATIENT: kubebackup may take a few minutes to install *** 3 | ********************************************************************************* 4 | -------------------------------------------------------------------------------- /helm/kubebackup/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "kubebackup.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "kubebackup.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "kubebackup.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | 34 | {{/* 35 | Create the name of the service account to use 36 | */}} 37 | {{- define "kubebackup.serviceAccountName" -}} 38 | {{- if .Values.serviceAccount.create -}} 39 | {{ default (include "kubebackup.fullname" .) .Values.serviceAccount.name }} 40 | {{- else -}} 41 | {{ default "default" .Values.serviceAccount.name }} 42 | {{- end -}} 43 | {{- end -}} 44 | -------------------------------------------------------------------------------- /helm/kubebackup/templates/clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app: {{ template "kubebackup.name" . }} 6 | chart: {{ template "kubebackup.chart" . }} 7 | heritage: {{ .Release.Service }} 8 | release: {{ .Release.Name }} 9 | name: {{ template "kubebackup.fullname" . }} 10 | namespace: {{ .Release.Namespace }} 11 | rules: 12 | - apiGroups: 13 | - '*' 14 | resources: 15 | - '*' 16 | verbs: 17 | - '*' 18 | - nonResourceURLs: 19 | - '*' 20 | verbs: 21 | - '*' 22 | -------------------------------------------------------------------------------- /helm/kubebackup/templates/clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app: {{ template "kubebackup.name" . }} 6 | chart: {{ template "kubebackup.chart" . }} 7 | heritage: {{ .Release.Service }} 8 | release: {{ .Release.Name }} 9 | name: {{ template "kubebackup.fullname" . }} 10 | roleRef: 11 | apiGroup: rbac.authorization.k8s.io 12 | kind: ClusterRole 13 | name: {{ template "kubebackup.fullname" . }} 14 | subjects: 15 | - kind: ServiceAccount 16 | name: {{ template "kubebackup.serviceAccountName" . }} 17 | namespace: {{ .Release.Namespace }} 18 | -------------------------------------------------------------------------------- /helm/kubebackup/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ template "kubebackup.fullname" . }} 5 | {{- if .Values.annotations }} 6 | annotations: 7 | {{ toYaml .Values.annotations | indent 4 }} 8 | {{- end }} 9 | labels: 10 | app: {{ template "kubebackup.name" . }} 11 | chart: {{ template "kubebackup.chart" . }} 12 | release: {{ .Release.Name }} 13 | heritage: {{ .Release.Service }} 14 | {{- if .Values.labels }} 15 | {{ toYaml .Values.labels | indent 4 }} 16 | {{- end }} 17 | spec: 18 | replicas: {{ .Values.replicaCount }} 19 | strategy: 20 | rollingUpdate: 21 | maxSurge: {{ .Values.strategy.rollingUpdate.maxSurge | default 1 }} 22 | maxUnavailable: {{ .Values.strategy.rollingUpdate.maxUnavailable | default 0 }} 23 | type: {{ .Values.strategy.type | default "RollingUpdate" }} 24 | selector: 25 | matchLabels: 26 | app: {{ template "kubebackup.name" . }} 27 | release: {{ .Release.Name }} 28 | template: 29 | metadata: 30 | labels: 31 | app: {{ template "kubebackup.name" . }} 32 | release: {{ .Release.Name }} 33 | {{- if .Values.podAnnotations }} 34 | annotations: 35 | {{ toYaml .Values.podAnnotations | indent 8 }} 36 | {{- end }} 37 | spec: 38 | {{- if .Values.securityContext }} 39 | securityContext: 40 | {{ toYaml .Values.securityContext | indent 8 }} 41 | {{- end }} 42 | serviceAccountName: {{ template "kubebackup.serviceAccountName" . }} 43 | containers: 44 | - name: {{ .Chart.Name }} 45 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 46 | imagePullPolicy: {{ .Values.image.pullPolicy }} 47 | env: 48 | {{- with .Values.env }} 49 | {{- toYaml . | indent 10 }} 50 | {{- end }} 51 | envFrom: 52 | {{- with .Values.envFrom }} 53 | {{- range . }} 54 | - {{ toYaml . | nindent 10 }} 55 | {{- end }} 56 | {{- end }} 57 | ports: 58 | - name: metrics 59 | containerPort: {{ .Values.metrics.port | default 8080 }} 60 | protocol: TCP 61 | resources: 62 | {{ toYaml .Values.resources | indent 10 }} 63 | {{- if .Values.image.pullSecrets }} 64 | imagePullSecrets: 65 | {{- range .Values.image.pullSecrets }} 66 | - name: {{ . }} 67 | {{- end }} 68 | {{- end }} 69 | {{- if .Values.nodeSelector }} 70 | nodeSelector: 71 | {{ toYaml .Values.nodeSelector | indent 8 }} 72 | {{- end }} 73 | {{- if .Values.tolerations }} 74 | tolerations: 75 | {{ toYaml .Values.tolerations | indent 8 }} 76 | {{- end }} 77 | {{- if .Values.affinity }} 78 | affinity: 79 | {{ toYaml .Values.affinity | indent 8 }} 80 | {{- end }} 81 | -------------------------------------------------------------------------------- /helm/kubebackup/templates/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | labels: 5 | app: {{ template "kubebackup.name" . }} 6 | chart: {{ template "kubebackup.chart" . }} 7 | heritage: {{ .Release.Service }} 8 | release: {{ .Release.Name }} 9 | name: {{ template "kubebackup.fullname" . }}-secret 10 | namespace: {{ .Release.Namespace }} 11 | type: Opaque 12 | data: 13 | s3-bucket: {{ .Values.s3.bucket | b64enc | quote }} 14 | s3-folder: {{ .Values.s3.folder | b64enc | quote }} 15 | s3-region: {{ .Values.s3.region | b64enc | quote }} 16 | s3-endpoint: {{ .Values.s3.endpoint | b64enc | quote }} 17 | s3-access-key": {{ .Values.s3.accessKey | b64enc | quote }} 18 | s3-secret-key: {{ .Values.s3.secretKey | b64enc | quote }} 19 | -------------------------------------------------------------------------------- /helm/kubebackup/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | labels: 6 | app: {{ template "kubebackup.name" . }} 7 | chart: {{ template "kubebackup.chart" . }} 8 | heritage: {{ .Release.Service }} 9 | release: {{ .Release.Name }} 10 | name: {{ template "kubebackup.serviceAccountName" . }} 11 | namespace: {{ .Release.Namespace }} 12 | {{- end -}} 13 | -------------------------------------------------------------------------------- /helm/kubebackup/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for KubeBackup 2 | # This is a YAML-formatted file. 3 | # Declare name/value pairs to be passed into your templates. 4 | # name: value 5 | 6 | image: 7 | repository: docker.io/cube8021/kubebackup 8 | tag: latest 9 | pullPolicy: IfNotPresent 10 | pullSecrets: [] 11 | 12 | replicaCount: 1 13 | 14 | annotations: {} 15 | labels: 16 | kubernetes.io/name: "KubeBackup" 17 | 18 | extraEnv: [] 19 | podAnnotations: {} 20 | 21 | nodeSelector: {} 22 | 23 | tolerations: [] 24 | 25 | affinity: {} 26 | 27 | resources: 28 | limits: 29 | cpu: 100m 30 | memory: 100Mi 31 | requests: 32 | cpu: 100m 33 | memory: 100Mi 34 | 35 | rbac: 36 | create: true 37 | 38 | clusterAdminRole: true 39 | 40 | secrets: 41 | s3: 42 | bucket: my-bucket 43 | folder: "my-cluster" 44 | region: us-east-1 45 | endpoint: "" 46 | accessKey: "" 47 | secretKey: "" 48 | 49 | serviceAccount: 50 | create: true 51 | name: kubebackup 52 | 53 | securityContext: {} 54 | 55 | settings: 56 | interval: 12h 57 | retention: 30d 58 | backupDir: /var/lib/kubebackup 59 | loglevel: info 60 | 61 | metrics: 62 | enabled: true 63 | port: 9009 64 | path: /metrics 65 | alertmanager: 66 | enabled: false 67 | url: "" 68 | interval: 5m 69 | labels: 70 | severity: warning 71 | annotations: 72 | summary: "KubeBackup alert" 73 | description: "KubeBackup is not running" -------------------------------------------------------------------------------- /helm/kubebackup/values.yaml.template: -------------------------------------------------------------------------------- 1 | # Default values for KubeBackup 2 | # This is a YAML-formatted file. 3 | # Declare name/value pairs to be passed into your templates. 4 | # name: value 5 | 6 | image: 7 | repository: docker.io/cube8021/kubebackup 8 | tag: ${IMAGE_TAG} 9 | pullPolicy: IfNotPresent 10 | pullSecrets: [] 11 | 12 | replicaCount: 1 13 | 14 | annotations: {} 15 | labels: 16 | kubernetes.io/name: "KubeBackup" 17 | 18 | extraEnv: [] 19 | podAnnotations: {} 20 | 21 | nodeSelector: {} 22 | 23 | tolerations: [] 24 | 25 | affinity: {} 26 | 27 | resources: 28 | limits: 29 | cpu: 100m 30 | memory: 100Mi 31 | requests: 32 | cpu: 100m 33 | memory: 100Mi 34 | 35 | rbac: 36 | create: true 37 | 38 | clusterAdminRole: true 39 | 40 | secrets: 41 | s3: 42 | bucket: my-bucket 43 | folder: "my-cluster" 44 | region: us-east-1 45 | endpoint: "" 46 | accessKey: "" 47 | secretKey: "" 48 | 49 | serviceAccount: 50 | create: true 51 | name: kubebackup 52 | 53 | securityContext: {} 54 | 55 | settings: 56 | interval: 12h 57 | retention: 30d 58 | backupDir: /var/lib/kubebackup 59 | loglevel: info 60 | 61 | metrics: 62 | enabled: true 63 | port: 9009 64 | path: /metrics 65 | alertmanager: 66 | enabled: false 67 | url: "" 68 | interval: 5m 69 | labels: 70 | severity: warning 71 | annotations: 72 | summary: "KubeBackup alert" 73 | description: "KubeBackup is not running" -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "net/http" 9 | "os" 10 | "os/signal" 11 | "sync" 12 | "syscall" 13 | "time" 14 | 15 | "github.com/mattmattox/kubebackup/pkg/backup" 16 | "github.com/mattmattox/kubebackup/pkg/config" 17 | "github.com/mattmattox/kubebackup/pkg/k8s" 18 | "github.com/mattmattox/kubebackup/pkg/logging" 19 | "github.com/mattmattox/kubebackup/pkg/version" 20 | "github.com/prometheus/client_golang/prometheus" 21 | "github.com/prometheus/client_golang/prometheus/promhttp" 22 | "github.com/robfig/cron/v3" 23 | "k8s.io/client-go/dynamic" 24 | "k8s.io/client-go/kubernetes" 25 | ) 26 | 27 | var ( 28 | logger = logging.SetupLogging() 29 | taskLock sync.Mutex 30 | isTaskRunning bool 31 | lastBackupInfo = struct { 32 | Status string `json:"status"` 33 | Message string `json:"message"` 34 | Time string `json:"time"` 35 | }{ 36 | Status: "unknown", 37 | Message: "No backups have been run yet.", 38 | Time: "", 39 | } 40 | 41 | // Prometheus Metrics 42 | lastBackupStatus = prometheus.NewGauge(prometheus.GaugeOpts{Name: "last_backup_status", Help: "The status of the last backup: 1 for success, 0 for failure."}) 43 | lastBackupTime = prometheus.NewGauge(prometheus.GaugeOpts{Name: "last_backup_timestamp", Help: "Last successful backup timestamp."}) 44 | lastBackupDuration = prometheus.NewGauge(prometheus.GaugeOpts{Name: "last_backup_duration_seconds", Help: "Duration of the last backup in seconds."}) 45 | ) 46 | 47 | func init() { 48 | // Register Prometheus Metrics 49 | prometheus.MustRegister(lastBackupStatus, lastBackupTime, lastBackupDuration) 50 | } 51 | 52 | func main() { 53 | flag.Parse() // Parse command-line flags 54 | 55 | // Load configuration from environment variables 56 | config.LoadConfiguration() 57 | 58 | logging.SetupLogging() 59 | 60 | // Validate configuration 61 | if err := validateConfig(); err != nil { 62 | logger.Fatalf("Configuration validation failed: %v", err) 63 | } 64 | 65 | // Connect to the Kubernetes cluster 66 | clientset, dynamicClient, err := k8s.ConnectToCluster(config.CFG.Kubeconfig) 67 | if err != nil { 68 | logger.Fatalf("Error creating clientset: %v", err) 69 | } 70 | 71 | // Verify access to the cluster 72 | err = k8s.VerifyAccessToCluster(clientset) 73 | if err != nil { 74 | logger.Fatalf("Error verifying access to cluster: %v", err) 75 | } 76 | 77 | // Start HTTP server for admin and metrics 78 | logger.Println("Starting HTTP server for metrics and admin endpoints...") 79 | server := startHTTPServer(clientset, dynamicClient) 80 | 81 | // Context to handle shutdown signals 82 | ctx, cancel := context.WithCancel(context.Background()) 83 | defer cancel() 84 | 85 | // Listen for OS signals 86 | signalChan := make(chan os.Signal, 1) 87 | signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM) 88 | 89 | // Handle RunOnce flag 90 | if config.CFG.RunOnce { 91 | logger.Println("RunOnce flag is enabled. Performing a single backup and exiting.") 92 | performBackup(clientset, dynamicClient) 93 | logger.Println("Task execution completed. Exiting...") 94 | if err := server.Shutdown(context.Background()); err != nil { 95 | logger.Printf("Error during server shutdown: %v", err) 96 | } 97 | return 98 | } 99 | 100 | // Handle DisableCron flag 101 | if config.CFG.DisableCron { 102 | logger.Println("Cron scheduling is disabled. Exiting.") 103 | return 104 | } 105 | 106 | // Create and start a cron scheduler 107 | c := cron.New() 108 | _, err = c.AddFunc(config.CFG.CronSchedule, func() { 109 | logger.Println("Starting scheduled backup...") 110 | performBackup(clientset, dynamicClient) 111 | }) 112 | if err != nil { 113 | logger.Fatalf("Error adding cron job: %v", err) 114 | } 115 | 116 | logger.Println("Starting cron scheduler...") 117 | c.Start() 118 | 119 | // Wait for termination signals 120 | go func() { 121 | <-signalChan 122 | logger.Println("Received shutdown signal, stopping cron scheduler...") 123 | c.Stop() 124 | cancel() 125 | }() 126 | 127 | // Wait for the context to be canceled 128 | <-ctx.Done() 129 | 130 | logger.Println("Exiting gracefully.") 131 | } 132 | 133 | // validateConfig ensures required fields are set in the configuration. 134 | func validateConfig() error { 135 | if config.CFG.CronSchedule == "" { 136 | return fmt.Errorf("CronSchedule cannot be empty") 137 | } 138 | if config.CFG.BackupTarget == "s3" { 139 | if config.CFG.S3AccessKeyID == "" || config.CFG.S3SecretAccessKey == "" { 140 | return fmt.Errorf("S3 configuration is incomplete: missing AccessKeyID or SecretAccessKey") 141 | } 142 | if config.CFG.S3Bucket == "" { 143 | return fmt.Errorf("S3 configuration is incomplete: missing Bucket") 144 | } 145 | } 146 | return nil 147 | } 148 | 149 | // performBackup triggers the backup process and updates metrics/status. 150 | func performBackup(clientset *kubernetes.Clientset, dynamicClient dynamic.Interface) { 151 | startTime := time.Now() 152 | 153 | status, err := backup.StartBackup(clientset, dynamicClient, &config.CFG) 154 | duration := time.Since(startTime) 155 | 156 | if err != nil { 157 | logger.Printf("Backup failed: %v", err) 158 | lastBackupInfo = struct { 159 | Status string `json:"status"` 160 | Message string `json:"message"` 161 | Time string `json:"time"` 162 | }{ 163 | Status: "failed", 164 | Message: err.Error(), 165 | Time: startTime.Format(time.RFC3339), 166 | } 167 | lastBackupStatus.Set(0) 168 | return 169 | } 170 | 171 | if status { 172 | logger.Printf("Backup completed successfully in %v", duration) 173 | lastBackupInfo = struct { 174 | Status string `json:"status"` 175 | Message string `json:"message"` 176 | Time string `json:"time"` 177 | }{ 178 | Status: "success", 179 | Message: "Backup completed successfully.", 180 | Time: startTime.Format(time.RFC3339), 181 | } 182 | lastBackupStatus.Set(1) 183 | lastBackupTime.Set(float64(startTime.Unix())) 184 | lastBackupDuration.Set(duration.Seconds()) 185 | } else { 186 | logger.Printf("Backup completed with errors in %v", duration) 187 | lastBackupInfo = struct { 188 | Status string `json:"status"` 189 | Message string `json:"message"` 190 | Time string `json:"time"` 191 | }{ 192 | Status: "failed", 193 | Message: "Backup completed with errors.", 194 | Time: startTime.Format(time.RFC3339), 195 | } 196 | lastBackupStatus.Set(0) 197 | } 198 | } 199 | 200 | // startHTTPServer starts an HTTP server for metrics and admin endpoints 201 | func startHTTPServer(clientset *kubernetes.Clientset, dynamicClient dynamic.Interface) *http.Server { 202 | logger.Println("Setting up HTTP server...") 203 | mux := http.NewServeMux() 204 | 205 | mux.HandleFunc("/", defaultPage) 206 | mux.Handle("/metrics", promhttp.Handler()) 207 | mux.HandleFunc("/healthz", healthCheck) 208 | mux.HandleFunc("/version", versionInfo) 209 | mux.HandleFunc("/backup", func(w http.ResponseWriter, r *http.Request) { 210 | logger.Printf("HTTP request to /backup from %s", r.RemoteAddr) 211 | if triggerAPITask(w, clientset, dynamicClient, "backup") { 212 | w.WriteHeader(http.StatusOK) 213 | fmt.Fprintf(w, "Backup triggered successfully at %s.\n", time.Now().Format(time.RFC3339)) 214 | } 215 | }) 216 | mux.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) { 217 | logger.Printf("HTTP request to /status from %s", r.RemoteAddr) 218 | w.Header().Set("Content-Type", "application/json") 219 | json.NewEncoder(w).Encode(lastBackupInfo) 220 | }) 221 | 222 | server := &http.Server{ 223 | Addr: fmt.Sprintf(":%d", config.CFG.MetricsPort), 224 | Handler: logRequestMiddleware(mux), 225 | ReadTimeout: 10 * time.Second, 226 | WriteTimeout: 10 * time.Second, 227 | IdleTimeout: 30 * time.Second, 228 | } 229 | 230 | go func() { 231 | logger.Printf("HTTP server running on port %d", config.CFG.MetricsPort) 232 | if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { 233 | logger.Fatalf("HTTP server failed: %v", err) 234 | } 235 | }() 236 | return server 237 | } 238 | 239 | // defaultPage returns a simple HTML page with links to various endpoints 240 | func defaultPage(w http.ResponseWriter, _ *http.Request) { 241 | w.Header().Set("Content-Type", "text/html") 242 | fmt.Fprint(w, ` 243 | 244 | 245 | KubeBackup 246 | 247 |

KubeBackup

248 | 254 | 255 | 256 | `) 257 | } 258 | 259 | // healthCheck returns a simple health check response 260 | func healthCheck(w http.ResponseWriter, _ *http.Request) { 261 | w.WriteHeader(http.StatusOK) 262 | fmt.Fprint(w, "ok") 263 | } 264 | 265 | // versionInfo returns the version information of the application 266 | func versionInfo(w http.ResponseWriter, _ *http.Request) { 267 | w.Header().Set("Content-Type", "application/json") 268 | err := json.NewEncoder(w).Encode(map[string]string{ 269 | "version": version.Version, 270 | "gitCommit": version.GitCommit, 271 | "buildTime": version.BuildTime, 272 | }) 273 | if err != nil { 274 | logger.Printf("Failed to encode version info: %v", err) 275 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 276 | return 277 | } 278 | } 279 | 280 | // logRequestMiddleware logs incoming HTTP requests 281 | func logRequestMiddleware(next http.Handler) http.Handler { 282 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 283 | logger.Printf("Incoming request: %s %s from %s", r.Method, r.URL.Path, r.RemoteAddr) 284 | next.ServeHTTP(w, r) 285 | }) 286 | } 287 | 288 | // triggerAPITask triggers a task based on the mode and returns true if the task was started 289 | func triggerAPITask(w http.ResponseWriter, clientset *kubernetes.Clientset, dynamicClient dynamic.Interface, mode string) bool { 290 | taskLock.Lock() 291 | defer taskLock.Unlock() 292 | 293 | if isTaskRunning { 294 | logger.Printf("Task already running; skipping %s request.", mode) 295 | http.Error(w, "Another task is already running", http.StatusConflict) 296 | return false 297 | } 298 | 299 | isTaskRunning = true 300 | go func() { 301 | defer func() { 302 | taskLock.Lock() 303 | isTaskRunning = false 304 | taskLock.Unlock() 305 | logger.Printf("Task for mode %s completed.", mode) 306 | }() 307 | 308 | startTime := time.Now() 309 | logger.Printf("Starting %s task...", mode) 310 | 311 | switch mode { 312 | case "backup": 313 | performBackup(clientset, dynamicClient) 314 | default: 315 | logger.Printf("Invalid task mode: %s", mode) 316 | http.Error(w, "Invalid task mode", http.StatusBadRequest) 317 | } 318 | 319 | duration := time.Since(startTime) 320 | logger.Printf("%s task completed in %v", mode, duration) 321 | }() 322 | 323 | return true 324 | } 325 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | .PHONY: install build test run docker-build 2 | 3 | install: 4 | go get -d -v ./... 5 | 6 | build: 7 | go get -d -v ./... 8 | go build -v -o kubebackup 9 | 10 | test: 11 | go test -v ./... 12 | 13 | run: 14 | go run main.go 15 | 16 | docker-build: 17 | docker build -t cube8021/kubebackup:latest . 18 | 19 | compile: 20 | GOOS=linux GOARCH=amd64 go build -o bin/kubebackup-linux-amd64 main.go 21 | GOOS=linux GOARCH=arm64 go build -o bin/kubebackup-linux-arm64 main.go 22 | -------------------------------------------------------------------------------- /pkg/backup/backup.go: -------------------------------------------------------------------------------- 1 | package backup 2 | 3 | import ( 4 | "archive/tar" 5 | "compress/gzip" 6 | "context" 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "os" 11 | "path/filepath" 12 | "sync" 13 | "time" 14 | 15 | "github.com/mattmattox/kubebackup/pkg/config" 16 | "github.com/mattmattox/kubebackup/pkg/k8s" 17 | "github.com/mattmattox/kubebackup/pkg/logging" 18 | "github.com/mattmattox/kubebackup/pkg/s3" 19 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 20 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 21 | "k8s.io/apimachinery/pkg/runtime/schema" 22 | "k8s.io/client-go/dynamic" 23 | "k8s.io/client-go/kubernetes" 24 | ) 25 | 26 | var log = logging.SetupLogging() 27 | 28 | func StartBackup(clientset *kubernetes.Clientset, dynamicClient dynamic.Interface, cfg *config.AppConfig) (bool, error) { 29 | log.Infoln("Fetching namespaces...") 30 | namespaces, err := k8s.GetNamespaces(clientset) 31 | if err != nil { 32 | return false, fmt.Errorf("error fetching namespaces: %v", err) 33 | } 34 | log.Infof("Found %d namespaces.", len(namespaces)) 35 | 36 | // Create a temporary directory for storing backup files 37 | tmpDir, err := os.MkdirTemp("", "kubebackup-") 38 | if err != nil { 39 | return false, fmt.Errorf("error creating temporary directory: %v", err) 40 | } 41 | log.Infof("Created temporary directory: %s", tmpDir) 42 | 43 | // Ensure the temporary directory is cleaned up after the tar operation 44 | defer func() { 45 | log.Infof("Cleaning up temporary directory: %s", tmpDir) 46 | os.RemoveAll(tmpDir) 47 | }() 48 | 49 | // Process cluster-scoped resources 50 | log.Infoln("Fetching cluster-scoped resources...") 51 | clusterScopedResources, err := k8s.GetClusterScopedResources(clientset) 52 | if err != nil { 53 | return false, fmt.Errorf("error fetching cluster-scoped resources: %v", err) 54 | } 55 | log.Infof("Found %d cluster-scoped resources.", len(clusterScopedResources)) 56 | 57 | clusterScopedDir := filepath.Join(tmpDir, "cluster-scoped") 58 | log.Infoln("Processing cluster-scoped resources...") 59 | if err := ProcessClusterScopedResources(dynamicClient, clusterScopedResources, clusterScopedDir); err != nil { 60 | return false, fmt.Errorf("error processing cluster-scoped resources: %v", err) 61 | } 62 | log.Infof("Cluster-scoped resources processed successfully.") 63 | 64 | // Process namespace-scoped resources 65 | log.Infoln("Fetching namespaced resources...") 66 | namespacedResources, err := k8s.GetNamespaceScopedResources(clientset) 67 | if err != nil { 68 | return false, fmt.Errorf("error fetching namespaced resources: %v", err) 69 | } 70 | log.Infof("Found %d namespaced resources.", len(namespacedResources)) 71 | 72 | namespaceScopedDir := filepath.Join(tmpDir, "namespace-scoped") 73 | log.Infoln("Processing namespace-scoped resources...") 74 | if err := ProcessNamespaces(dynamicClient, namespaces, namespacedResources, namespaceScopedDir); err != nil { 75 | return false, fmt.Errorf("error processing namespace-scoped resources: %v", err) 76 | } 77 | log.Infof("Namespace-scoped resources processed successfully.") 78 | 79 | // Compress the backup directory 80 | tarFilePath, err := CompressBackup(tmpDir) 81 | if err != nil { 82 | return false, fmt.Errorf("error during compression: %v", err) 83 | } 84 | 85 | if cfg.BackupTarget == "s3" { 86 | // Compress and upload to S3 87 | log.Infoln("Uploading backup to S3...") 88 | if err := s3.UploadToS3(tarFilePath, cfg.S3Bucket, cfg.S3Folder, cfg.S3Region, cfg.S3AccessKeyID, cfg.S3SecretAccessKey, cfg.S3Endpoint, cfg.S3CustomCA, cfg.S3DisableSSL, cfg.Retention); err != nil { 89 | return false, fmt.Errorf("error compressing and uploading to S3: %v", err) 90 | } 91 | 92 | // Delete the tarball after uploading to S3 93 | if err := os.Remove(tarFilePath); err != nil { 94 | log.Errorf("Failed to delete tarball: %v", err) 95 | } 96 | } else { 97 | log.Infof("Backup tarball created at: %s", tarFilePath) 98 | } 99 | 100 | // Ensure cleanup of the temporary directory 101 | defer func() { 102 | if cleanupErr := CleanupTmpDir(tmpDir); cleanupErr != nil { 103 | log.Errorf("Failed to clean up temporary directory: %v", cleanupErr) 104 | } 105 | }() 106 | 107 | log.Infof("Backup process completed successfully.") 108 | return true, nil 109 | } 110 | 111 | func ProcessNamespaces(dynamicClient dynamic.Interface, namespaces []string, namespacedResources []schema.GroupVersionResource, baseDir string) error { 112 | var wg sync.WaitGroup 113 | var processErr error 114 | mu := &sync.Mutex{} 115 | 116 | for _, ns := range namespaces { 117 | wg.Add(1) 118 | go func(ns string) { 119 | defer wg.Done() 120 | if err := processNamespace(dynamicClient, ns, namespacedResources, baseDir); err != nil { 121 | mu.Lock() 122 | defer mu.Unlock() 123 | processErr = fmt.Errorf("error processing namespace '%s': %w", ns, err) 124 | log.Errorf("Error processing namespace '%s': %v", ns, err) 125 | } 126 | }(ns) 127 | } 128 | 129 | wg.Wait() 130 | 131 | return processErr 132 | } 133 | 134 | func ProcessClusterScopedResources(dynamicClient dynamic.Interface, resources []schema.GroupVersionResource, baseDir string) error { 135 | // Create the base directory for cluster-scoped resources 136 | if err := os.MkdirAll(baseDir, 0755); err != nil { 137 | return fmt.Errorf("error creating directory '%s': %v", baseDir, err) 138 | } 139 | 140 | for _, resource := range resources { 141 | log.Infof("Processing cluster-scoped resource: %s", resource.Resource) 142 | 143 | // Fetch objects for the resource 144 | objects, err := GetClusterObjects(dynamicClient, resource) 145 | if err != nil { 146 | log.Errorf("Error fetching objects for resource '%s': %v", resource.Resource, err) 147 | continue 148 | } 149 | 150 | // Skip if no objects are found 151 | if len(objects) == 0 { 152 | log.Infof("No objects found for resource '%s'", resource.Resource) 153 | continue 154 | } 155 | 156 | // Create a directory for the resource 157 | resourceDir := filepath.Join(baseDir, resource.Resource) 158 | if err := os.MkdirAll(resourceDir, 0755); err != nil { 159 | log.Errorf("Error creating directory '%s': %v", resourceDir, err) 160 | continue 161 | } 162 | 163 | for _, object := range objects { 164 | objectName := object.GetName() 165 | objectFile := filepath.Join(resourceDir, objectName+".yaml") 166 | 167 | objectData, err := object.MarshalJSON() 168 | if err != nil { 169 | log.Errorf("Error marshalling object '%s': %v", objectName, err) 170 | continue 171 | } 172 | 173 | if err := writeObject(objectData, objectFile); err != nil { 174 | log.Errorf("Error writing object '%s' to file '%s': %v", objectName, objectFile, err) 175 | continue 176 | } 177 | } 178 | } 179 | 180 | return nil 181 | } 182 | 183 | func GetClusterObjects(dynamicClient dynamic.Interface, resource schema.GroupVersionResource) ([]*unstructured.Unstructured, error) { 184 | // Fetch the list of objects for the resource 185 | resourceList, err := dynamicClient.Resource(resource).List(context.TODO(), metav1.ListOptions{}) 186 | if err != nil { 187 | return nil, fmt.Errorf("error fetching resource list for '%s': %v", resource.Resource, err) 188 | } 189 | 190 | // Convert items to []*unstructured.Unstructured 191 | objects := make([]*unstructured.Unstructured, 0, len(resourceList.Items)) 192 | for i := range resourceList.Items { 193 | objects = append(objects, &resourceList.Items[i]) 194 | } 195 | 196 | return objects, nil 197 | } 198 | 199 | func processNamespace(dynamicClient dynamic.Interface, ns string, namespacedResources []schema.GroupVersionResource, baseDir string) error { 200 | log.Infof("Processing namespace %s", ns) 201 | namespaceDir := fmt.Sprintf("%s/%s", baseDir, ns) 202 | if err := os.MkdirAll(namespaceDir, 0755); err != nil { 203 | return fmt.Errorf("error creating directory '%s': %v", namespaceDir, err) 204 | } 205 | 206 | for _, resource := range namespacedResources { 207 | if err := processResource(dynamicClient, ns, resource, namespaceDir); err != nil { 208 | log.Errorf("Error processing resource '%s' in namespace '%s': %v", resource.Resource, ns, err) 209 | } 210 | } 211 | return nil 212 | } 213 | 214 | func processResource(dynamicClient dynamic.Interface, ns string, resource schema.GroupVersionResource, namespaceDir string) error { 215 | log.Infof("Processing resource %s in namespace %s", resource.Resource, ns) 216 | objects, err := k8s.GetNamespaceObjects(dynamicClient, ns, resource, "") 217 | if err != nil { 218 | return fmt.Errorf("error fetching objects for resource '%s' in namespace '%s': %v", resource.Resource, ns, err) 219 | } 220 | 221 | log.Infof("Found %d objects for resource %s in namespace %s", len(objects), resource.Resource, ns) 222 | for _, object := range objects { 223 | if err := processObject(dynamicClient, ns, resource, object, namespaceDir); err != nil { 224 | log.Errorf("Error processing object '%s' of resource '%s': %v", object, resource.Resource, err) 225 | } 226 | } 227 | return nil 228 | } 229 | 230 | func processObject(dynamicClient dynamic.Interface, ns string, resource schema.GroupVersionResource, object string, namespaceDir string) error { 231 | log.Infof("Processing object %s of resource %s in namespace %s", object, resource.Resource, ns) 232 | 233 | // Create the directory for the resource 234 | objectDir := filepath.Join(namespaceDir, resource.Resource) 235 | if err := os.MkdirAll(objectDir, 0755); err != nil { 236 | return fmt.Errorf("error creating directory '%s': %v", objectDir, err) 237 | } 238 | 239 | // Fetch the object data 240 | objectData, objectName, err := getObject(dynamicClient, ns, resource, object) 241 | if err != nil { 242 | return fmt.Errorf("error fetching object '%s': %v", object, err) 243 | } 244 | 245 | // Write the object data to a YAML file 246 | objectFilePath := filepath.Join(objectDir, objectName+".yaml") 247 | if err := writeObject(objectData, objectFilePath); err != nil { 248 | return fmt.Errorf("error writing object '%s': %v", objectName, err) 249 | } 250 | return nil 251 | } 252 | 253 | func getObject(dynamicClient dynamic.Interface, ns string, resource schema.GroupVersionResource, object string) ([]byte, string, error) { 254 | // Fetch the object 255 | objectData, err := dynamicClient.Resource(resource).Namespace(ns).Get(context.TODO(), object, metav1.GetOptions{}) 256 | if err != nil { 257 | return nil, "", fmt.Errorf("error fetching object '%s': %v", object, err) 258 | } 259 | 260 | // Extract object name and marshal data to JSON 261 | objectName := objectData.GetName() 262 | objectJSON, err := json.Marshal(objectData.Object) 263 | if err != nil { 264 | return nil, "", fmt.Errorf("error converting object '%s' to JSON: %v", object, err) 265 | } 266 | return objectJSON, objectName, nil 267 | } 268 | 269 | func writeObject(objectData []byte, objectFile string) error { 270 | // Create the file 271 | f, err := os.Create(objectFile) 272 | if err != nil { 273 | return fmt.Errorf("error creating file '%s': %v", objectFile, err) 274 | } 275 | defer f.Close() 276 | 277 | // Write the data to the file 278 | if _, err := f.Write(objectData); err != nil { 279 | return fmt.Errorf("error writing to file '%s': %v", objectFile, err) 280 | } 281 | 282 | return nil 283 | } 284 | 285 | // CompressBackup creates a tarball of the source directory and compresses it using gzip. 286 | func CompressBackup(srcDir string) (string, error) { 287 | timestamp := time.Now().Format("2006-01-02_15-04-05") 288 | tarFilePath := filepath.Join("/tmp/", fmt.Sprintf("kubebackup_%s.tar.gz", timestamp)) 289 | 290 | log.Debugf("Starting compression process for directory: %s", srcDir) 291 | log.Debugf("Generated tarball file path: %s", tarFilePath) 292 | 293 | // Create the tarball 294 | log.Infof("Creating tarball at: %s", tarFilePath) 295 | err := createTarball(srcDir, tarFilePath) 296 | if err != nil { 297 | log.Errorf("Error during tarball creation for directory %s: %v", srcDir, err) 298 | return "", fmt.Errorf("error creating tarball: %v", err) 299 | } 300 | 301 | log.Infof("Successfully created tarball: %s", tarFilePath) 302 | return tarFilePath, nil 303 | } 304 | 305 | func createTarball(srcDir, tarFilePath string) error { 306 | tarFile, err := os.Create(tarFilePath) 307 | if err != nil { 308 | return fmt.Errorf("error creating tar file: %v", err) 309 | } 310 | defer tarFile.Close() 311 | 312 | gzipWriter := gzip.NewWriter(tarFile) 313 | defer gzipWriter.Close() 314 | 315 | tarWriter := tar.NewWriter(gzipWriter) 316 | defer tarWriter.Close() 317 | 318 | return filepath.Walk(srcDir, func(file string, info os.FileInfo, err error) error { 319 | if err != nil { 320 | return fmt.Errorf("error walking file path: %v", err) 321 | } 322 | 323 | // Skip the tarball file itself 324 | if file == tarFilePath { 325 | return nil 326 | } 327 | 328 | // Create a tar header 329 | relPath, err := filepath.Rel(srcDir, file) 330 | if err != nil { 331 | return fmt.Errorf("error calculating relative path: %v", err) 332 | } 333 | 334 | header, err := tar.FileInfoHeader(info, relPath) 335 | if err != nil { 336 | return fmt.Errorf("error creating tar header: %v", err) 337 | } 338 | header.Name = relPath 339 | 340 | // Write the header 341 | if err := tarWriter.WriteHeader(header); err != nil { 342 | return fmt.Errorf("error writing tar header: %v", err) 343 | } 344 | 345 | // Skip directories 346 | if info.IsDir() { 347 | return nil 348 | } 349 | 350 | // Write the file content 351 | fileHandle, err := os.Open(file) 352 | if err != nil { 353 | return fmt.Errorf("error opening file: %v", err) 354 | } 355 | defer fileHandle.Close() 356 | 357 | if _, err := io.Copy(tarWriter, fileHandle); err != nil { 358 | return fmt.Errorf("error writing file content to tar: %v", err) 359 | } 360 | 361 | return nil 362 | }) 363 | } 364 | 365 | func CleanupTmpDir(tmpDir string) error { 366 | log.Infof("Cleaning up temporary directory: %s", tmpDir) 367 | 368 | // Remove the temporary directory and its contents 369 | if err := os.RemoveAll(tmpDir); err != nil { 370 | return fmt.Errorf("error cleaning up temporary directory '%s': %v", tmpDir, err) 371 | } 372 | 373 | log.Infof("Temporary directory %s cleaned up successfully.", tmpDir) 374 | return nil 375 | } 376 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | // AppConfig structure for environment-based configurations. 12 | type AppConfig struct { 13 | Debug bool `json:"debug"` 14 | LogLevel string `json:"log_level"` 15 | MetricsPort int `json:"metricsPort"` 16 | Kubeconfig string `json:"kubeconfig"` 17 | BackupDir string `json:"backup_dir"` 18 | CronSchedule string `json:"cron_schedule"` 19 | DisableCron bool `json:"disable_cron"` 20 | RunOnce bool `json:"run_once"` 21 | Retention int `json:"retention"` 22 | BackupTarget string `json:"backup_target"` 23 | S3Endpoint string `json:"s3Endpoint"` 24 | S3AccessKeyID string `json:"s3AccessKeyID"` 25 | S3SecretAccessKey string `json:"s3SecretAccessKey"` 26 | S3Bucket string `json:"s3Bucket"` 27 | S3Region string `json:"s3Region"` 28 | S3Folder string `json:"s3_folder"` 29 | S3DisableSSL bool `json:"s3_disable_ssl"` 30 | S3CustomCA string `json:"s3_custom_ca"` 31 | S3CustomCAPath string `json:"s3_custom_ca_path"` 32 | } 33 | 34 | // CFG is the global configuration object. 35 | var CFG AppConfig 36 | 37 | // LoadConfiguration loads configuration from environment variables. 38 | func LoadConfiguration() { 39 | CFG.Debug = parseEnvBool("DEBUG", false) 40 | CFG.MetricsPort = parseEnvInt("METRICS_PORT", 9999) 41 | CFG.S3Endpoint = getEnvOrDefault("S3_ENDPOINT", "s3.us-central-1.wasabisys.com") 42 | CFG.S3AccessKeyID = getEnvOrDefault("S3_ACCESS_KEY_ID", "") 43 | CFG.S3SecretAccessKey = getEnvOrDefault("S3_SECRET_ACCESS_KEY", "") 44 | CFG.S3Bucket = getEnvOrDefault("S3_BUCKET", "") 45 | CFG.S3Region = getEnvOrDefault("S3_REGION", "") 46 | CFG.S3Folder = getEnvOrDefault("S3_FOLDER", "") 47 | CFG.S3DisableSSL = parseEnvBool("S3_DISABLE_SSL", false) 48 | CFG.S3CustomCA = getEnvOrDefault("S3_CUSTOM_CA", "") 49 | CFG.S3CustomCAPath = getEnvOrDefault("S3_CUSTOM_CA_PATH", "") 50 | CFG.BackupDir = getEnvOrDefault("BACKUP_DIR", "/pvc") 51 | CFG.Retention = parseEnvInt("RETENTION", 30) 52 | CFG.CronSchedule = getEnvOrDefault("CRON_SCHEDULE", "0 0 * * *") 53 | CFG.DisableCron = parseEnvBool("DISABLE_CRON", false) 54 | CFG.RunOnce = parseEnvBool("RUN_ONCE", false) 55 | CFG.BackupTarget = getEnvOrDefault("BACKUP_TARGET", "s3") 56 | CFG.Kubeconfig = getEnvOrDefault("KUBECONFIG", "~/.kube/config") 57 | CFG.LogLevel = getEnvOrDefault("LOG_LEVEL", "info") 58 | } 59 | 60 | func getEnvOrDefault(key, defaultValue string) string { 61 | value := os.Getenv(key) 62 | if value == "" { 63 | return defaultValue 64 | } 65 | return value 66 | } 67 | 68 | func parseEnvInt(key string, defaultValue int) int { 69 | value := os.Getenv(key) 70 | if value == "" { 71 | return defaultValue 72 | } 73 | var intValue int 74 | _, err := fmt.Sscanf(value, "%d", &intValue) 75 | if err != nil { 76 | log.Printf("Failed to parse environment variable %s: %v. Using default value: %d", key, err, defaultValue) 77 | return defaultValue 78 | } 79 | return intValue 80 | } 81 | 82 | func parseEnvBool(key string, defaultValue bool) bool { 83 | value, exists := os.LookupEnv(key) 84 | if !exists { 85 | return defaultValue 86 | } 87 | value = strings.ToLower(value) 88 | 89 | // Handle additional truthy and falsy values 90 | switch value { 91 | case "1", "t", "true", "yes", "on", "enabled": 92 | return true 93 | case "0", "f", "false", "no", "off", "disabled": 94 | return false 95 | default: 96 | boolValue, err := strconv.ParseBool(value) 97 | if err != nil { 98 | log.Printf("Error parsing %s as bool: %v. Using default value: %t", key, err, defaultValue) 99 | return defaultValue 100 | } 101 | return boolValue 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /pkg/k8s/k8s.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/mattmattox/kubebackup/pkg/logging" 9 | 10 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "k8s.io/apimachinery/pkg/runtime/schema" 12 | "k8s.io/client-go/discovery" 13 | "k8s.io/client-go/dynamic" 14 | "k8s.io/client-go/kubernetes" 15 | "k8s.io/client-go/rest" 16 | "k8s.io/client-go/tools/clientcmd" 17 | ) 18 | 19 | var log = logging.SetupLogging() 20 | 21 | // ConnectToCluster connects to the Kubernetes cluster and returns both *kubernetes.Clientset and dynamic.Interface 22 | func ConnectToCluster(kubeconfig string) (*kubernetes.Clientset, dynamic.Interface, error) { 23 | var config *rest.Config 24 | var err error 25 | 26 | // Use in-cluster config if available 27 | if os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "" { 28 | config, err = rest.InClusterConfig() 29 | if err != nil { 30 | return nil, nil, fmt.Errorf("error creating in-cluster config: %v", err) 31 | } 32 | } else { 33 | // Fall back to kubeconfig 34 | if kubeconfig == "" { 35 | kubeconfig = os.Getenv("KUBECONFIG") 36 | if kubeconfig == "" { 37 | kubeconfig = fmt.Sprintf("%s/.kube/config", os.Getenv("HOME")) 38 | } 39 | } 40 | config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) 41 | if err != nil { 42 | return nil, nil, fmt.Errorf("error creating kubeconfig: %v", err) 43 | } 44 | } 45 | 46 | // Create the *kubernetes.Clientset 47 | clientset, err := kubernetes.NewForConfig(config) 48 | if err != nil { 49 | return nil, nil, fmt.Errorf("error creating clientset: %v", err) 50 | } 51 | 52 | // Create the dynamic.Interface 53 | dynamicClient, err := dynamic.NewForConfig(config) 54 | if err != nil { 55 | return nil, nil, fmt.Errorf("error creating dynamic client: %v", err) 56 | } 57 | 58 | return clientset, dynamicClient, nil 59 | } 60 | 61 | // VerifyAccessToCluster verifies the connection to the Kubernetes cluster by listing nodes. 62 | func VerifyAccessToCluster(clientset *kubernetes.Clientset) error { 63 | log.Infoln("Verifying access to the Kubernetes cluster...") 64 | ctx := context.TODO() 65 | listOptions := v1.ListOptions{} 66 | 67 | _, err := clientset.CoreV1().Nodes().List(ctx, listOptions) 68 | if err != nil { 69 | return fmt.Errorf("error listing nodes: %v", err) 70 | } 71 | 72 | log.Infoln("Access to the Kubernetes cluster verified successfully.") 73 | return nil 74 | } 75 | 76 | func GetNamespaces(clientset *kubernetes.Clientset) ([]string, error) { 77 | namespaceList, err := clientset.CoreV1().Namespaces().List(context.Background(), v1.ListOptions{}) 78 | if err != nil { 79 | return nil, err 80 | } 81 | namespaces := make([]string, len(namespaceList.Items)) 82 | for i, namespace := range namespaceList.Items { 83 | namespaces[i] = namespace.GetName() 84 | } 85 | return namespaces, nil 86 | } 87 | 88 | // GetNamespaceScopedResources returns a list of namespaced resources as []schema.GroupVersionResource 89 | func GetNamespaceScopedResources(clientset *kubernetes.Clientset) ([]schema.GroupVersionResource, error) { 90 | discoveryClient := clientset.Discovery() 91 | apiResourceLists, err := discoveryClient.ServerPreferredNamespacedResources() 92 | if err != nil { 93 | if discovery.IsGroupDiscoveryFailedError(err) { 94 | // Handle partial discovery errors 95 | fmt.Printf("Partial discovery error: %v\n", err) 96 | } else { 97 | return nil, fmt.Errorf("error fetching namespaced resources: %v", err) 98 | } 99 | } 100 | 101 | var resources []schema.GroupVersionResource 102 | for _, apiResourceList := range apiResourceLists { 103 | groupVersion, err := schema.ParseGroupVersion(apiResourceList.GroupVersion) 104 | if err != nil { 105 | return nil, fmt.Errorf("error parsing GroupVersion %s: %v", apiResourceList.GroupVersion, err) 106 | } 107 | for _, apiResource := range apiResourceList.APIResources { 108 | if apiResource.Namespaced { 109 | resources = append(resources, groupVersion.WithResource(apiResource.Name)) 110 | } 111 | } 112 | } 113 | 114 | return resources, nil 115 | } 116 | 117 | // GetClusterScopedResources fetches all cluster-scoped resources 118 | func GetClusterScopedResources(clientset *kubernetes.Clientset) ([]schema.GroupVersionResource, error) { 119 | discoveryClient := clientset.Discovery() 120 | apiResourceLists, err := discoveryClient.ServerPreferredResources() 121 | if err != nil { 122 | if discovery.IsGroupDiscoveryFailedError(err) { 123 | fmt.Printf("Partial discovery error: %v\n", err) 124 | } else { 125 | return nil, fmt.Errorf("error fetching cluster-scoped resources: %v", err) 126 | } 127 | } 128 | 129 | var resources []schema.GroupVersionResource 130 | for _, apiResourceList := range apiResourceLists { 131 | groupVersion, err := schema.ParseGroupVersion(apiResourceList.GroupVersion) 132 | if err != nil { 133 | return nil, fmt.Errorf("error parsing GroupVersion %s: %v", apiResourceList.GroupVersion, err) 134 | } 135 | 136 | for _, apiResource := range apiResourceList.APIResources { 137 | if !apiResource.Namespaced { // Only include cluster-scoped resources 138 | resources = append(resources, groupVersion.WithResource(apiResource.Name)) 139 | } 140 | } 141 | } 142 | 143 | return resources, nil 144 | } 145 | 146 | func GetNamespacedObjects(clientset *kubernetes.Clientset) ([]schema.GroupVersionResource, error) { 147 | log.Infoln("Fetching namespaced API resources...") 148 | apiResourceList, err := clientset.Discovery().ServerPreferredResources() 149 | if err != nil { 150 | log.Errorf("Error fetching namespaced API resources: %v", err) 151 | return nil, err 152 | } 153 | log.Debugln("API Resources: ", apiResourceList) 154 | 155 | var gv schema.GroupVersion 156 | objects := make([]schema.GroupVersionResource, 0) 157 | for _, apiResources := range apiResourceList { 158 | gv, _ = schema.ParseGroupVersion(apiResources.GroupVersion) // Use = for assignment 159 | for _, apiResource := range apiResources.APIResources { 160 | if apiResource.Namespaced { 161 | // Create a GroupVersionResource with the API version included 162 | object := gv.WithResource(apiResource.Name) 163 | objects = append(objects, object) 164 | } 165 | } 166 | } 167 | log.Debugln("Namespaced Objects: ", objects) 168 | return objects, nil 169 | } 170 | 171 | // GetNamespaceObjects retrieves the list of object names for a specific resource in a namespace. 172 | func GetNamespaceObjects(dynamicClient dynamic.Interface, ns string, resource schema.GroupVersionResource, apiVersion string) ([]string, error) { 173 | // List the objects for the given resource 174 | resourceList, err := dynamicClient.Resource(resource).Namespace(ns).List(context.TODO(), v1.ListOptions{}) 175 | if err != nil { 176 | return nil, fmt.Errorf("error listing objects for resource %s in namespace %s: %v", resource.Resource, ns, err) 177 | } 178 | 179 | // Extract the names of the objects 180 | objectNames := make([]string, len(resourceList.Items)) 181 | for i, obj := range resourceList.Items { 182 | objectNames[i] = obj.GetName() 183 | } 184 | 185 | return objectNames, nil 186 | } 187 | 188 | func GetAPIVersionForResource(clientset *kubernetes.Clientset, resource schema.GroupVersionResource) (string, error) { 189 | // Get the API resource 190 | apiResource, err := clientset.Discovery().ServerResourcesForGroupVersion(resource.GroupVersion().String()) 191 | if err != nil { 192 | return "", err 193 | } 194 | // Get the API version 195 | apiVersion := apiResource.APIResources[0].Version 196 | return apiVersion, nil 197 | } 198 | -------------------------------------------------------------------------------- /pkg/logging/logging.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/sirupsen/logrus" 7 | ) 8 | 9 | var logger *logrus.Logger 10 | 11 | // SetupLogging initializes the logger with the appropriate settings. 12 | func SetupLogging() *logrus.Logger { 13 | // Get the debug from environment variable 14 | debug := false 15 | if os.Getenv("DEBUG") == "true" { 16 | debug = true 17 | } 18 | if logger == nil { 19 | logger = logrus.New() 20 | logger.SetOutput(os.Stdout) 21 | logger.SetReportCaller(true) 22 | 23 | // Initialize a custom log formatter without timestamps 24 | customFormatter := new(logrus.TextFormatter) 25 | customFormatter.DisableTimestamp = true // Disable timestamp since k8s will handle it 26 | customFormatter.FullTimestamp = false 27 | logger.SetFormatter(customFormatter) 28 | 29 | // Set the logging level based on the debug environment variable 30 | if debug { 31 | logger.SetLevel(logrus.DebugLevel) 32 | } else { 33 | logger.SetLevel(logrus.InfoLevel) 34 | } 35 | } 36 | 37 | return logger 38 | } 39 | -------------------------------------------------------------------------------- /pkg/metrics/metrics.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/prometheus/client_golang/prometheus" 8 | "github.com/prometheus/client_golang/prometheus/promhttp" 9 | ) 10 | 11 | var ( 12 | backupDuration = prometheus.NewGauge(prometheus.GaugeOpts{ 13 | Name: "kubebackup_backup_duration_seconds", 14 | Help: "Duration of the backup process in seconds.", 15 | }) 16 | 17 | timeSinceLastBackup = prometheus.NewGauge(prometheus.GaugeOpts{ 18 | Name: "kubebackup_time_since_last_backup_seconds", 19 | Help: "Time since the last successful backup in seconds.", 20 | }) 21 | 22 | backupSuccess = prometheus.NewGauge(prometheus.GaugeOpts{ 23 | Name: "kubebackup_backup_success", 24 | Help: "Indicates whether the last backup was successful (1) or not (0).", 25 | }) 26 | 27 | objectCount = prometheus.NewCounterVec(prometheus.CounterOpts{ 28 | Name: "kubebackup_objects_count", 29 | Help: "Number of objects backed up per object type.", 30 | }, []string{"object_type"}) 31 | 32 | namespacesTotal = prometheus.NewGauge(prometheus.GaugeOpts{ 33 | Name: "kubebackup_namespaces_total", 34 | Help: "Total number of namespaces being backed up.", 35 | }) 36 | ) 37 | 38 | func init() { 39 | prometheus.MustRegister(backupDuration) 40 | prometheus.MustRegister(timeSinceLastBackup) 41 | prometheus.MustRegister(backupSuccess) 42 | prometheus.MustRegister(objectCount) 43 | prometheus.MustRegister(namespacesTotal) 44 | } 45 | 46 | func StartMetricsServer(ctx context.Context, metricsPort string) error { 47 | mux := http.NewServeMux() 48 | mux.Handle("/metrics", promhttp.Handler()) 49 | 50 | server := &http.Server{ 51 | Addr: ":" + metricsPort, 52 | Handler: mux, 53 | } 54 | 55 | // Run the server in a goroutine 56 | go func() { 57 | <-ctx.Done() 58 | server.Shutdown(context.Background()) // Shutdown server on context cancellation 59 | }() 60 | 61 | return server.ListenAndServe() 62 | } 63 | 64 | func WriteBackupDuration(duration float64) { 65 | backupDuration.Set(duration) 66 | } 67 | 68 | func WriteTimeSinceLastBackup(duration float64) { 69 | timeSinceLastBackup.Set(duration) 70 | } 71 | 72 | func WriteBackupSuccess(success bool) { 73 | if success { 74 | backupSuccess.Set(1) 75 | } else { 76 | backupSuccess.Set(0) 77 | } 78 | } 79 | 80 | func WriteObjectCount(objectType string, count int) { 81 | objectCount.WithLabelValues(objectType).Add(float64(count)) 82 | } 83 | 84 | func WriteNamespaceCount(count int) { 85 | namespacesTotal.Set(float64(count)) 86 | } 87 | -------------------------------------------------------------------------------- /pkg/s3/s3.go: -------------------------------------------------------------------------------- 1 | package s3 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "fmt" 7 | "net/http" 8 | "os" 9 | "path/filepath" 10 | "time" 11 | 12 | "github.com/aws/aws-sdk-go/aws" 13 | "github.com/aws/aws-sdk-go/aws/credentials" 14 | "github.com/aws/aws-sdk-go/aws/session" 15 | "github.com/aws/aws-sdk-go/service/s3" 16 | "github.com/aws/aws-sdk-go/service/s3/s3manager" 17 | "github.com/mattmattox/kubebackup/pkg/logging" 18 | ) 19 | 20 | var log = logging.SetupLogging() 21 | 22 | func createS3Session(region, accessKey, secretKey, endpoint, caPath string, disableSSL bool) (*session.Session, error) { 23 | s3Config := &aws.Config{ 24 | Region: aws.String(region), 25 | Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""), 26 | } 27 | 28 | // Configure the endpoint and SSL settings 29 | if endpoint != "" { 30 | s3Config.Endpoint = aws.String(endpoint) 31 | s3Config.S3ForcePathStyle = aws.Bool(true) 32 | s3Config.DisableSSL = aws.Bool(disableSSL) 33 | } 34 | 35 | // Configure custom CA if provided 36 | if caPath != "" { 37 | caCertPool, err := loadCustomCACerts(caPath) 38 | if err != nil { 39 | return nil, fmt.Errorf("error loading custom CA certificates: %v", err) 40 | } 41 | s3Config.HTTPClient = &http.Client{ 42 | Transport: &http.Transport{ 43 | TLSClientConfig: &tls.Config{ 44 | RootCAs: caCertPool, 45 | }, 46 | }, 47 | } 48 | } 49 | 50 | // Create and return the session 51 | return session.NewSession(s3Config) 52 | } 53 | 54 | func loadCustomCACerts(caPath string) (*x509.CertPool, error) { 55 | caCertPool := x509.NewCertPool() 56 | 57 | // Read the custom CA certificate 58 | caCert, err := os.ReadFile(caPath) 59 | if err != nil { 60 | return nil, fmt.Errorf("error reading CA certificate file: %v", err) 61 | } 62 | 63 | // Append the CA certificate to the cert pool 64 | if !caCertPool.AppendCertsFromPEM(caCert) { 65 | return nil, fmt.Errorf("failed to append CA certificate to pool") 66 | } 67 | 68 | return caCertPool, nil 69 | } 70 | 71 | func uploadToS3(sess *session.Session, bucket, key, filename, s3Folder string) error { 72 | log.Infof("Uploading file: %s", filename) 73 | // Open the file for reading 74 | file, err := os.Open(filename) 75 | if err != nil { 76 | return err 77 | } 78 | defer file.Close() 79 | 80 | uploader := s3manager.NewUploader(sess) 81 | 82 | s3Key := fmt.Sprintf("%s/%s", s3Folder, key) 83 | 84 | _, err = uploader.Upload(&s3manager.UploadInput{ 85 | Bucket: aws.String(bucket), 86 | Key: aws.String(s3Key), 87 | Body: file, 88 | }) 89 | if err != nil { 90 | return fmt.Errorf("failed to upload file, %v", err) 91 | } 92 | 93 | return nil 94 | } 95 | 96 | func cleanupOldBackups(sess *session.Session, s3Bucket, s3Folder string, retentionPeriod int) error { 97 | log.Infoln("Retrieving list of objects in S3 bucket...") 98 | 99 | log.Infoln("Retaining backups for", retentionPeriod, "days") 100 | 101 | svc := s3.New(sess) 102 | listObjectsInput := &s3.ListObjectsV2Input{ 103 | Bucket: aws.String(s3Bucket), 104 | Prefix: aws.String(fmt.Sprintf("%s/kubebackup-", s3Folder)), 105 | } 106 | 107 | objects, err := svc.ListObjectsV2(listObjectsInput) 108 | if err != nil { 109 | return fmt.Errorf("error listing objects in S3 bucket: %v", err) 110 | } 111 | 112 | threshold := time.Now().AddDate(0, 0, -retentionPeriod) 113 | for _, obj := range objects.Contents { 114 | if obj.LastModified.Before(threshold) { 115 | log.Infof("Deleting object %s from S3 bucket", *obj.Key) 116 | deleteObjectInput := &s3.DeleteObjectInput{ 117 | Bucket: aws.String(s3Bucket), 118 | Key: obj.Key, 119 | } 120 | 121 | _, err := svc.DeleteObject(deleteObjectInput) 122 | if err != nil { 123 | return fmt.Errorf("error deleting object from S3 bucket: %v", err) 124 | } 125 | } 126 | } 127 | 128 | return nil 129 | } 130 | 131 | func UploadToS3(tarFilePath, s3Bucket, s3Folder, s3Region, s3AccessKey, s3SecretKey, s3Endpoint, caPath string, disableSSL bool, retentionPeriod int) error { 132 | log.Infoln("Uploading tarball to S3...") 133 | 134 | // Create S3 session 135 | sess, err := createS3Session(s3Region, s3AccessKey, s3SecretKey, s3Endpoint, caPath, disableSSL) 136 | if err != nil { 137 | return fmt.Errorf("error creating S3 session: %v", err) 138 | } 139 | 140 | // Upload tarball 141 | timestamp := filepath.Base(tarFilePath) // Use the tarball name as the key 142 | s3Key := fmt.Sprintf("%s/%s", s3Folder, timestamp) 143 | if err := uploadToS3(sess, s3Bucket, s3Key, tarFilePath, s3Folder); err != nil { 144 | return fmt.Errorf("failed to upload tarball to S3: %v", err) 145 | } 146 | 147 | // Cleanup old backups 148 | if retentionPeriod > 0 { 149 | log.Infof("Cleaning up old backups older than %d days...", retentionPeriod) 150 | if err := cleanupOldBackups(sess, s3Bucket, s3Folder, retentionPeriod); err != nil { 151 | return fmt.Errorf("error cleaning up old backups: %v", err) 152 | } 153 | } 154 | 155 | log.Infof("Backup successfully uploaded to S3: %s/%s", s3Bucket, s3Key) 156 | return nil 157 | } 158 | -------------------------------------------------------------------------------- /pkg/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | // Variables set during build time 4 | var ( 5 | Version = "unknown" // Set via -ldflags during build 6 | GitCommit = "unknown" // Set via -ldflags during build 7 | BuildTime = "unknown" // Set via -ldflags during build 8 | ) 9 | --------------------------------------------------------------------------------