master 084eaf042dee cached
20 files
131.9 KB
52.0k tokens
28 symbols
1 requests
Download .txt
Repository: christian-korneck/docker-pushrm
Branch: master
Commit: 084eaf042dee
Files: 20
Total size: 131.9 KB

Directory structure:
gitextract_xhlei939/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   └── problem-or-bug.md
│   └── workflows/
│       ├── release.yml
│       └── webhook_target.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── README-containers.md
├── README.md
├── cmd/
│   ├── docker-cli-plugin-metadata.go
│   ├── pushrm.go
│   └── root.go
├── go.mod
├── go.sum
├── gon.json
├── main.go
├── provider/
│   ├── dockerhub/
│   │   └── dockerhub.go
│   ├── harbor2/
│   │   └── harbor2.go
│   ├── provider/
│   │   └── provider.go
│   └── quay/
│       └── quay.go
└── util/
    └── util.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/ISSUE_TEMPLATE/problem-or-bug.md
================================================
---
name: problem or bug
about: I have a problem with docker-pushrm
title: ''
labels: ''
assignees: ''

---

**Describe the problem or bug**
A clear and concise description of what the problem or bug is, including all steps necessary to reproduce it.

**docker-pushrm version**
check version with:
- as Docker CLI plugin: `docker --help | grep pushrm`
- as standalone: `docker-pushrm docker-cli-plugin-metadata | grep -i version`

**Docker CLI version and platform**
check with: `docker version`
if on Linux, what distro and distro version are you running? (example: Fedora 32)

**if possible: registry server version**
example: *self-hosted `harbor` version 2.0.0*

**exact command that you're running**
example: `docker pushrm --provider quay quay.io/my-user/my-repo`

**debug output**
re-run that command with `--debug` flag and paste the output here
(i.e.: `docker pushrm --provider quay quay.io/my-user/my-repo --debug`)

**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
  push:
    # Sequence of patterns matched against refs/tags
    tags:
      - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10

jobs:
  pre:
   name: pre
   runs-on: ubuntu-latest
   steps:
     - name: pre1
       run: |
         env
         exit 0
       
  cr_release:
    name: create_release
    needs: [pre] #don't create a release too early
    runs-on: ubuntu-latest
    steps:
    - name: Check out code for changelog creation
      uses: actions/checkout@v2
      with:
        fetch-depth: 0 #otherwise only one commit is fetched
    - name: Create Changelog
      id: create_changelog
      run: |
        git fetch --tags
        git tag #for debug
        git log --oneline #for debug
        previousTag=$(git tag --sort=-v:refname | head -2 | tail -1)
        echo previous tag: $previousTag #for debug
        changelog=$(git log --oneline --pretty="%s" $previousTag..HEAD)
        echo changelog1: "$changelog" #for debug
        changelog=$(echo "$changelog" | sed 's/^/- /')
        echo changelog2: "$changelog" #for debug
        echo changes since $previousTag: > ./changelog.md
        echo "$changelog" >> ./changelog.md
    - name: Create Release
      id: create_release
      uses: actions/create-release@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # token provided by Actions, no need to set it
      with:
        tag_name: ${{ github.ref }}
        release_name: Release ${{ github.ref }}
        draft: true #we'll undraft at the end if the build step was successfull (= all assets uploaded to the release)
        prerelease: false
        body_path: ./changelog.md
    - name: Output Release URL File
      run: echo "${{ steps.create_release.outputs.upload_url }}" > release_url.txt
    - name: Save Release URL File for publish
      uses: actions/upload-artifact@v1
      with:
        name: release_url
        path: ./release_url.txt
    - name: Output Release ID File
      run: echo "${{ steps.create_release.outputs.id }}" > release_id.txt
    - name: Save Release ID File for publish
      uses: actions/upload-artifact@v1
      with:
        name: release_id
        path: ./release_id.txt
        
  build:
    needs: [pre, cr_release]
    strategy:
      matrix:
        os: [linux, darwin, windows]
        arch: [amd64, 386, arm64, arm]
        exclude:
          - os: windows
            arch: arm64
          - os: windows
            arch: arm
          - os: darwin
            arch: arm
          - os: darwin
            arch: 386
        include:
          - os: windows
            file_extension: '.exe'
          - os: windows
            ci_image: ubuntu-latest
          - os: linux
            file_extension: ''
          - os: linux
            ci_image: ubuntu-latest
          - os: darwin
            file_extension: ''
          - os: darwin
            ci_image: macos-latest
          - os: darwin
            no_upx: true
    name: Build
    runs-on: ${{ matrix.ci_image }}
    env: 
      GOOS: ${{ matrix.os }}
      GOARCH: ${{ matrix.arch }}
    steps:

    - name: Set up Go 1.16
      uses: actions/setup-go@v2
      with:
        go-version: 1.16
      id: go

    - name: Check out code into the Go module directory
      uses: actions/checkout@v2

    - name: Get dependencies
      env:
        GOOS: ${{ matrix.os }}
        GOARCH: ${{ matrix.arch }}
        CGO_ENABLED: 0
      run: |
        go get -v -t -d .

    - name: Build
      run: go build -v -a -tags netgo -ldflags='-s -w -extldflags "-static"' .

    - name: compress with upx
      if: ${{ matrix.no_upx != true }}
      run: sudo apt-get -y update && sudo apt-get -y install upx && upx ./docker-pushrm${{ matrix.file_extension }}

    - name: Import darwin code-signing certificates
      if: ${{ matrix.os == 'darwin' }}
      uses: Apple-Actions/import-codesign-certs@v1
      with:
        p12-file-base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }} #dev id cert as b64
        p12-password: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }} #pw for cert

    - name: Sign darwin binary with Gon
      if: ${{ matrix.os == 'darwin' }}
      env:
        AC_USERNAME: ${{ secrets.AC_USERNAME }} #apple id
        AC_PASSWORD: ${{ secrets.AC_PASSWORD }} #app specific pw for apple id
      run: |
        brew tap mitchellh/gon
        brew install mitchellh/gon/gon
        gon -log-level=debug -log-json ./gon.json
        rm -f ./docker-pushrm
        unzip docker-pushrm

    - name: Upload artifact
      uses: actions/upload-artifact@v1
      with:
        name: docker-pushrm_${{ matrix.os }}_${{ matrix.arch }}${{ matrix.file_extension }}
        path: ./docker-pushrm${{ matrix.file_extension }}

    - name: Load Release URL File from release job
      uses: actions/download-artifact@v1
      with:
          name: release_url
    - name: Get Release File Name & Upload URL
      id: get_release_info
      run: |
        value=`cat release_url/release_url.txt`
        echo ::set-output name=upload_url::$value

    - name: Upload Release Asset
      id: upload-release-asset
      uses: actions/upload-release-asset@v1.0.1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        upload_url: ${{ steps.get_release_info.outputs.upload_url }} # references get_release_info step above
        asset_name: docker-pushrm_${{ matrix.os }}_${{ matrix.arch }}${{ matrix.file_extension }}
        asset_path: ./docker-pushrm${{ matrix.file_extension }}
        asset_content_type: application/octet-stream
  
  cr_darwin_universal_binary:
    needs: [build]
    runs-on: macos-latest
    steps:

    - name: Check out code
      uses: actions/checkout@v2

    - name: Download darwin_amd64 artifact
      uses: actions/download-artifact@v1
      with:
          name: docker-pushrm_darwin_amd64
    
    - name: Download darwin_arm64 artifact
      uses: actions/download-artifact@v1
      with:
          name: docker-pushrm_darwin_arm64
    
    - name: package darwin universal binary
      run: |
        lipo -create docker-pushrm_darwin_arm64/docker-pushrm docker-pushrm_darwin_amd64/docker-pushrm -output docker-pushrm
    
    - name: Import darwin code-signing certificates
      uses: Apple-Actions/import-codesign-certs@v1
      with:
        p12-file-base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }} #Apple Dev ID cert as b64
        p12-password: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }} #pw for cert

    - name: Sign darwin binary with Gon
      env:
        AC_USERNAME: ${{ secrets.AC_USERNAME }} #apple id
        AC_PASSWORD: ${{ secrets.AC_PASSWORD }} #app specific pw for apple id
      run: |
        brew tap mitchellh/gon
        brew install mitchellh/gon/gon
        gon -log-level=debug -log-json ./gon.json
        rm -f ./docker-pushrm
        unzip docker-pushrm

    - name: Upload artifact
      uses: actions/upload-artifact@v1
      with:
        name: docker-pushrm_darwin_universal2
        path: ./docker-pushrm

    - name: Load Release URL File from release job
      uses: actions/download-artifact@v1
      with:
          name: release_url
  
    - name: Get Release File Name & Upload URL
      id: get_release_info
      run: |
        value=`cat release_url/release_url.txt`
        echo ::set-output name=upload_url::$value

    - name: Upload Release Asset
      id: upload-release-asset
      uses: actions/upload-release-asset@v1.0.1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        upload_url: ${{ steps.get_release_info.outputs.upload_url }} # references get_release_info step above
        asset_name: docker-pushrm_darwin_universal2
        asset_path: ./docker-pushrm
        asset_content_type: application/octet-stream

  
  build_containers:
    #needs: [build]
    runs-on: ubuntu-latest
    steps:
    - name: Check out code
      uses: actions/checkout@v2
    - name: Split tag string into semantic version parts
      id: semver
      run: |
        git fetch --tags
        git tag #for debug
        export vcur=$(git tag --sort=-v:refname | head -1 | sed 's/v//1')
        export vmajor=$(echo $vcur | cut -d. -f1)
        export vminor=$(echo $vcur | cut -d. -f2)
        export vpatch=$(echo $vcur | cut -d. -f3)
        echo ::set-output name=vcur::$vcur
        echo ::set-output name=vmajor::$vmajor
        echo ::set-output name=vminor::$vminor
        echo ::set-output name=vpatch::$vpatch
        echo version: $vcur
        echo version major: $vmajor
        echo version minor: $vminor
        echo version patch: $vpatch
    - name: Prepare docker-buildx
      run: |
        docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
        docker buildx create --name mybuilder
        docker buildx ls #for debug
        docker buildx inspect --bootstrap
        docker buildx use mybuilder
    - name: Docker login
      env:
        DOCKER_USER: chko
        DOCKER_PASS: ${{ secrets.DOCKER_PASS }}
      run: |
        docker login -u "$DOCKER_USER" -p  "$DOCKER_PASS"
    - name: Build and Push container images with docker-buildx
      env:
        vcur: ${{ steps.semver.outputs.vcur }}
        vmajor: ${{ steps.semver.outputs.vmajor }}
        vminor: ${{ steps.semver.outputs.vminor }}
        vpatch: ${{ steps.semver.outputs.vpatch }}
      run: |
        docker buildx inspect #for debug
        echo version: $ver - major: $vmajor - minor: $vminor - patch: $vpatch #for debug
        destrepo=chko/docker-pushrm
        docker buildx build -t "$destrepo:latest" -t "$destrepo:$vcur" -t "$destrepo:$vmajor" --platform linux/amd64,linux/386,linux/arm64,linux/arm/v7,linux/arm/v6 --push .
        docker buildx stop
    - name: Update container repo README
      env:
        #exact env var names for docker-pushrm
        DOCKER_USER: chko
        DOCKER_PASS: ${{ secrets.DOCKER_PASS }}
      run: |
        # download latest docker-pushrm release from github
        # (because we keep the release that this workflow creates drafted until the end of the workflow run the version we're downloading is NOT the one we're currently releasing)
        export FILENAME=docker-pushrm_linux_amd64 && export DESTDIR=. && DOWNLOAD_URL=$(curl --silent --fail --show-error https://api.github.com/repos/christian-korneck/docker-pushrm/releases/latest | jq -r ".assets | map(select(.name == \"$FILENAME\"))[0].browser_download_url") && curl --silent --fail --show-error -L $DOWNLOAD_URL -o "$DESTDIR/docker-pushrm" && chmod +rx "$DESTDIR/docker-pushrm"
        # this automatically uses README-containers.md
        ./docker-pushrm --short "Docker Push Readme - Update the docs of your container repo on Dockerhub, Quay, Harbor from a file" chko/docker-pushrm

  undraft_release:
    needs: [build, build_containers, cr_darwin_universal_binary] #only undraft the release when assets were uploaded
    runs-on: ubuntu-latest
    steps:
    - name: Load Release ID File from release job
      uses: actions/download-artifact@v1
      with:
          name: release_id
    - name: Get Release ID
      id: get_release_id_info
      run: |
        value=`cat release_id/release_id.txt`
        echo ::set-output name=release_id::$value
    - name: Undraft release
      id: undraft_release
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        RELEASE_ID: ${{ steps.get_release_id_info.outputs.release_id }} # references other step
      run: |
        curl --verbose --fail --show-error --location --request PATCH "https://api.github.com/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID" --header "Authorization: token $GITHUB_TOKEN" --header 'Content-Type: application/json' --header 'Accept: application/vnd.github.everest-preview+json' --data-raw '{"draft": false}'
  
  cleanup:
    needs: [build, cr_darwin_universal_binary] #currently this can start before build_containers is finished
    if: always()
    runs-on: ubuntu-latest
    steps:
    - name: call webhook to delete artifacts
      env:
        FOR_WEBHOOKS_SECRET: ${{ secrets.FOR_WEBHOOKS_SECRET }}
      run: |
        echo "::add-mask::$FOR_WEBHOOKS_SECRET"
        curl --verbose --fail --show-error --location --request POST "https://api.github.com/repos/$GITHUB_REPOSITORY/dispatches" --header "Authorization: token $FOR_WEBHOOKS_SECRET" --header 'Content-Type: application/json' --header 'Accept: application/vnd.github.everest-preview+json' --data-raw "{ \"event_type\": \"delete_all_artifacts\", \"client_payload\": {\"parent_runid\": \"$GITHUB_RUN_ID\", \"parent_repo\": \"$GITHUB_REPOSITORY\"} }"


================================================
FILE: .github/workflows/webhook_target.yml
================================================
name: delete calling job's artifacts
on: repository_dispatch
jobs:
  main:
    runs-on: ubuntu-latest
    steps:
    - name: Delete artifacts
      if: github.event.action == 'delete_all_artifacts'
      uses: christian-korneck/delete-run-artifacts-action@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        parent_runid: ${{ github.event.client_payload.parent_runid  }}
        parent_repo: ${{ github.event.client_payload.parent_repo }}

================================================
FILE: .gitignore
================================================
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
/docker-pushrm

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/


================================================
FILE: Dockerfile
================================================
FROM golang:1.16-alpine AS builder
WORKDIR /go/src/github.com/christian-korneck/docker-pushrm
COPY . .
RUN apk add --no-cache ca-certificates && update-ca-certificates
ENV CGO_ENABLED 0
RUN go build -v -a -tags netgo -ldflags='-s -w -extldflags "-static"' .
#RUN apk add --no-cache upx && upx ./docker-pushrm

FROM docker.io/library/busybox
COPY --from=builder /go/src/github.com/christian-korneck/docker-pushrm/docker-pushrm /
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

#nobody:nogroup
USER 65534:65534
ENTRYPOINT ["/docker-pushrm"]


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2020 Christian Korneck

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README-containers.md
================================================
# Docker Push Readme

This is a container image of [`docker-pushrm`](https://github.com/christian-korneck/docker-pushrm), a tool that lets you update the README of your container repo on Dockerhub, Quay or Harbor from a local markdown file.

![hello moon](https://raw.githubusercontent.com/christian-korneck/docker-pushrm/master/assets/container_registries.png)

## About this tool

Check the [full docs](https://github.com/christian-korneck/docker-pushrm/blob/master/README.md) for an introduction.

## How to use this container image

### Examples

#### Push a README file to Dockerhub

```
$ ls
README.md

$ docker run --rm -t \
-v $(pwd):/myvol \
-e DOCKER_USER='my-user' -e DOCKER_PASS='my-pass' \
chko/docker-pushrm:1 --file /myvol/README.md \ 
--short "My short description" --debug my-user/my-repo

...
DEBU content validation successfull, readme successfully pushed to repo server
```

Let's dissect this a bit:

- we pin to the major version (`v1`) of this image (`chko/docker-pushrm:v1`). Recommended for most use cases.
- the README file is in the current working directory on the host. We map this dir as volume to the container (mounted at `/myvol`)
- we tell *docker-pushrm* where to find the README file with `--file <path>`
- our destination repo on Dockerhub is `my-user/my-repo`
- we pass the credentials for the repo as environment variables to the container (`DOCKER_USER` and `DOCKER_PASS`)
- we set `--debug` to get additional log output (optional)
- we set the short description for the Dockerhub repo with `--short <string>` (optional)

**Alternatively all params can also get set with environment variables:**

```
$ docker run --rm -t \
-v $(pwd):/myvol \
-e DOCKER_USER='my-user' -e DOCKER_PASS='my-pass' \
-e PUSHRM_PROVIDER=dockerhub -e PUSHRM_FILE=/myvol/README.md \
-e PUSHRM_SHORT='my short description' \
-e PUSHRM_TARGET=docker.io/my-user/my-repo -e PUSHRM_DEBUG=1 \
chko/docker-pushrm:1
```

#### Push a README file to a Harbor v2 registry server

Use the `--provider harbor2` flag:

```
$ ls
README.md

$ docker run --rm -t \
-v $(pwd):/myvol \
-e DOCKER_USER='my-user' -e DOCKER_PASS='my-pass' \
chko/docker-pushrm:1 --file /myvol/README.md \ 
--provider harbor2 --debug demo.goharbor.io/my-project/my-repo
```

#### Push a README file to Quay.io or a Quay registry server


- use the `--provider quay` flag
- use env var `APIKEY__<SERVER>_<DOMAIN>` or `DOCKER_APIKEY` for apikey credentials

```
$ ls
README.md

$ docker run --rm -t \
-v $(pwd):/myvol \
-e APIKEY__QUAY_IO='my-apikey' \
chko/docker-pushrm:1 --file /myvol/README.md \ 
--provider quay --debug quay.io/my-user/my-repo
```

### env vars

| env var                     | example value                  | description
| --------------------------- | ------------------------------ | ----------------------------------------
| `DOCKER_USER`               | `my-user`                      | login username
| `DOCKER_PASS`               | `my-password`                  | login password
| `DOCKER_APIKEY`             | `my-quay-api-key`              | quay api key
| `APIKEY__<SERVER>_<DOMAIN>` | `my-quay-api-key`              | quay api key (alternative)
| `PUSHRM_PROVIDER`           | `dockerhub`, `quay`, `harbor2` | repo provider type
| `PUSHRM_SHORT`              | `my short description`         | set/update repo short description
| `PUSHRM_FILE`               | `/myvol/README.md`             | path to the README file
| `PUSHRM_DEBUG`              | `1`                            | enable verbose output
| `PUSHRM_CONFIG`             | `/myvol/.docker/config.json`   | Docker config file (for credentials)
| `PUSHRM_TARGET`             | `docker.io/my-user/my-repo`    | container repo ref

Presedence:
- Params specified with flags take precedence over env vars.
- Login env vars take precedence over credentials from a Docker config file







================================================
FILE: README.md
================================================
# Docker Push Readme

Update the README of your container repo on Dockerhub, Quay or Harbor with a simple Docker command:

```
$ ls
README.md
$ docker pushrm my-user/hello-world
```

![hello moon](assets/container_registries.png)

## About

`docker-pushrm` is a Docker CLI plugin that adds a new `docker pushrm` (speak: *"push readme"*) command to Docker.

It pushes the README file from the current working directory to a container registry server where it appears as repo description in the webinterface.

It currently supports **[Dockerhub](https://hub.docker.com)** (cloud), **Red Hat Quay** ([cloud](https://quay.io) and [self-hosted](https://www.openshift.com/products/quay)/OpenShift) and **[Harbor v2](https://goharbor.io)** (self-hosted).

For most registry types `docker-pushrm` uses authentication info from the Docker credentials store - so it "just works" for registry servers that you're already logged into with Docker.

(For some other registry types, you'll need to pass an API key via env var or config file).

## Usage example

Let's build a container image, push it to Dockerhub and then also push the README to Dockerhub:

```
$ ls
Dockerfile	README.md
$ docker login
Username: my-user
Password: ********
Login Succeeded
$ docker build -t my-user/hello-world .
$ docker push my-user/hello-world
$ docker pushrm my-user/hello-world
```

When we now browse to the repo in the Dockerhub webinterface we should find the repo's README to be updated with the contents of the local README file.

The same works for Harbor version 2 registry servers:

```
docker pushrm --provider harbor2 demo.goharbor.io/myproject/hello-world
```

And also for Quay/OpenShift cloud and self-hosted registry servers:
```
docker pushrm --provider quay quay.io/my-user/hello-world
```

For Dockerhub it's also possible to set the repo's short description with `-s "some description"`.

In case that you want different content to appear in the README on the container registry than on the git repo (for github/gitlab), you can create a dedicated `README-containers.md`, which takes precedence. It's also possible to specify a path to a README file with `--file <path>`.

## Installation

- make sure Docker or Docker Desktop is installed
- Download `docker-pushrm` for your platform from the [release page](https://github.com/christian-korneck/docker-pushrm/releases/latest).
- copy it to:
  - Windows: `c:\Users\<your-username>\.docker\cli-plugins\docker-pushrm.exe`
  - Mac + Linux: `$HOME/.docker/cli-plugins/docker-pushrm`
- on Mac/Linux make it executable: `chmod +x $HOME/.docker/cli-plugins/docker-pushrm`

Now you should be able to run `docker pushrm --help`.

## Running `docker-pushrm` as a container

There's also a Docker/OCI [container image](https://hub.docker.com/r/chko/docker-pushrm) of this tool. See [separate docs](README-containers.md) for how to use it. This is mainly intended for use in CI workflows.

## Use with github actions

This tool is also available as a github action [here](https://github.com/marketplace/actions/update-container-description-action).

## Use with GitLab CI/CD

Here's an example for a `.gitlab-ci.yml` that uses the `docker-pushrm` container image. (`DOCKER_USER` and `DOCKER_PASS` need to be set as project or group variables):

```
stages:
  - release

pushrm:
  stage: release
  image:
    name: chko/docker-pushrm
    entrypoint: ["/bin/sh", "-c", "/docker-pushrm"]
  variables:
    DOCKER_USER: $DOCKER_USER
    DOCKER_PASS: $DOCKER_PASS
    PUSHRM_SHORT: My short description
    PUSHRM_TARGET: docker.io/$DOCKER_USER/my-repo
    PUSHRM_DEBUG: 1
    PUSHRM_FILE: $CI_PROJECT_DIR/README.md
  script: "/bin/true"
```

(Note: The above `entrypoint`/`script` setup is a workaround for a [GitLab limitation](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/26501). For the same reason the `docker-pushrm` container images include [busybox](https://hub.docker.com/_/busybox)).

## How to log in to container registries

### Log in to Dockerhub registry

```
docker login
```

Both password and Personal Access Token (PAT) should work. When using a PAT, make sure it has sufficient privileges (`admin` scope).

### Log in to Harbor v2 registry

In the Harbor webinterface, create a `Robot Account` for your project with (at least) the privilege `repository`: `update` [[screenshot](https://github.com/christian-korneck/docker-pushrm/issues/10#issuecomment-2159212629)] and use the displayed username and password.

(Login with a regular Harbor user account instead is possible too, but [won't work](https://github.com/christian-korneck/docker-pushrm/issues/10) if the Harbor instance is using OIDC auth. Using a robot account is strongly recommended).


```
docker login <servername>
```

Example:
```
docker login demo.goharbor.io
```

### Log in to Quay registry

If you want to be able to push containers, you need to log in as usual:

- for Quay cloud: `docker login quay.io`
- for self-hosted Quay server or OpenShift: `docker login <servername>` (example: `docker login my-server.com`)

In addition to be able to use `docker-pushrm` you need to set up an API key:

First, log into the Quay webinterface and create an API key:
- if you don't have an organization create a new organization (your repos don't need to be under the organization's namespace, this is just to unlock the "apps" settings page)
- navigate to the org and open the `applications` tab
- `create new app` and give it some name
- click on the app name and open to the `generate token` tab
- create a token with permissions `Read/Write to any accessible repositories`
- after confirming you should now see the token secret. Write it down in a safe place.

(Refer to the Quay docs for more info)

Then, make the API key available to `docker-pushrm`. There are two options for that: Either set an environment variable (recommended for CI) or add it to the Docker config file (recommended for Desktop use). (If both are present, the env var takes precedence).

#### env var for Quay API key
set an environment variable `DOCKER_APIKEY=<apikey>` or `APIKEY__<SERVERNAME>_<DOMAIN>=<apikey>`

example for servername `quay.io`:
```
export APIKEY__QUAY_IO=my-api-key
docker pushrm quay.io/my-user/my-repo
```

#### configure Quay API key in Docker config file

In the Docker config file (default: `$HOME/.docker/config.json`) add a json key `plugins.docker-pushrm.apikey_<servername>` with the api key as string value.

Example for servername `quay.io`:

```
{

  ...,


  "plugins" : {
    "docker-pushrm" : {
      "apikey_quay.io" : "my-api-key"
    }
  },

  ...
}
```

## Log in with environment variables (for CI)

Alternatively credentials can be set as environment variables. Environment variables take precedence over the Docker credentials store. Environment variables can be specified with or without a server name. The variant without a server name takes precedence.

This is intended for running `docker-pushrm` as a standalone tool in a CI environment (no full Docker installation needed).

- `DOCKER_USER` and `DOCKER_PASS`
- `DOCKER_USER__<SERVER>_<DOMAIN>` and `DOCKER_PASS__<SERVER>_<DOMAIN>`
	(example for server `docker.io`: `DOCKER_USER__DOCKER_IO=my-user` and `DOCKER_PASS__DOCKER_IO=my-password`)

The provider 'quay' needs an additional env var for the API key in form of `APIKEY__<SERVERNAME>_<DOMAIN>=<apikey>`.

Example:

```
DOCKER_USER=my-user DOCKER_PASS=mypass docker-pushrm my-user/my-repo
```


## What if I use [podman, img, k3c, buildah, ...] instead of Docker?

You can still use `docker-pushrm` as standalone executable.

The only obstacle is that you need to provide it credentials in the Docker style.

The easiest way for that is to set up a minimal Docker config file with the registry server logins that you need. (Alternatively credentials can be passed [in environment variables](#log-in-with-environment-variables-for-ci) )

You can either create this config file on a computer with Docker installed (by running `docker login` and then copying the `$HOME/.docker/config.json` file).

Or alternatively you can also set it up manually. Here's an example:

```
{
	"auths": {
		"https://index.docker.io/v1/": {
			"auth": "xxx"
		},
        "https://demo.goharbor.io": {
			"auth": "xxx"
		}

	},
}
```
The auth value is base64 of `<user>:<passwd>` (i.e. `myuser:mypasswd`)

It's also possible to use Docker [credential helpers](https://docs.docker.com/engine/reference/commandline/login/#credential-helpers) on systems that don't have Docker installed to avoid clear text passwords in the config file. The credential helper needs to be configured in the Docker config file and the credential helper executable needs to be in the `PATH`. (Check the Docker docs for details).

## Can you add support for registry [XY...]?

Please open an issue.

## Installation for all users

To install the plugin for all users of a system copy it to the following path (instead of to the user home dir). Requires admin/root privs.

- Linux: depending on the distro, either `/usr/lib/docker/cli-plugins/docker-pushrm` or `/usr/libexec/docker/cli-plugins/docker-pushrm`
- Windows: `%ProgramData%\Docker\cli-plugins\docker-pushrm.exe`
- Mac: `/Applications/Docker.app/Contents/Resources/cli-plugins/docker-pushrm`

On Mac/Linux make it executable and readable for all users: `chmod a+rx <path>/docker-pushrm`

## Using env vars instead of cmdline params

All cmdline parameters can also be set as env vars with prefix `PUSHRM_`.    
    
Cmdline parameters take precedence over env vars. (Except for login env vars, which take precedence over the local credentials store).    
    
This is mainly intended for running this tool in a container in [12fa](https://12factor.net/config) style.    
    
A list of all supported env vars is [here](README-containers.md#env-vars).

## Limitations

(currently none)

----
All trademarks, logos and website designs belong to their respective owners.


================================================
FILE: cmd/docker-cli-plugin-metadata.go
================================================
/*
Copyright © 2020 Christian Korneck <christian@korneck.de>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

package cmd

import (
	"encoding/json"
	"os"

	"github.com/spf13/cobra"
)

// dockerCliPluginMetadataCmd represents the dockerCliPluginMetadata command
var dockerCliPluginMetadataCmd = &cobra.Command{
	Use:   "docker-cli-plugin-metadata",
	Short: "provides plugin metadata to the docker cli",
	Long: `provides plugin metadata to the docker cli
	
	`,
	Run: func(cmd *cobra.Command, args []string) {
		enc := json.NewEncoder(os.Stdout)
		enc.SetIndent("", "    ")
		// specs: https://docs.docker.com/engine/extend/cli_plugins/#the-docker-cli-plugin-metadata-subcommand
		d := map[string]string{"SchemaVersion": "0.1.0", "Vendor": "Christian Korneck", "Version": "1.9.0", "ShortDescription": "Push Readme to container registry"}
		enc.Encode(d)
	},
}

func init() {
	rootCmd.AddCommand(dockerCliPluginMetadataCmd)
	dockerCliPluginMetadataCmd.Hidden = true

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// dockerCliPluginMetadataCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// dockerCliPluginMetadataCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}


================================================
FILE: cmd/pushrm.go
================================================
/*
Copyright © 2020 Christian Korneck <christian@korneck.de>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

package cmd

import (
	"errors"
	"os"
	"regexp"
	"strings"
	"unicode/utf8"

	"github.com/christian-korneck/docker-pushrm/provider/dockerhub"
	"github.com/christian-korneck/docker-pushrm/provider/harbor2"
	"github.com/christian-korneck/docker-pushrm/provider/provider"
	"github.com/christian-korneck/docker-pushrm/provider/quay"
	"github.com/christian-korneck/docker-pushrm/util"
	log "github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var providername string
var rfile string
var shortdesc string

// pushrmCmd represents the pushrm command
var pushrmCmd = &cobra.Command{
	Use:     " NAME[:TAG]",
	Aliases: []string{"pushrm"},
	Args:    cobra.MaximumNArgs(1),
	Short:   "push README file from current working directory to container registry (Dockerhub, quay, harbor2)",
	Long: `help for docker pushrm

	docker pushrm NAME[:TAG] [flags]

	pushes the README.md file from the current working
	directory to the container registry (Dockerhub, quay, harbor2)
	where it appears as repo description.



	Usage Examples:
	===============


	Dockerhub (hub.docker.com cloud)
	--------------------------------
	docker pushrm myaccount/hello-world
	docker pushrm docker.io/my-account/hello-world


	Quay (quay.io cloud or self-hosted)
	-----------------------------------
	docker pushrm quay.io/my-organization/hello-world
	docker pushrm --provider quay my-quay-server.com/my-user/hello-world


	Harbor (self-hosted)
	--------------------
	docker pushrm --provider harbor2 my-harbor-server.com/my-project/hello-world



	How to login
	=============


	For most registry providers docker-pushrm uses
	the registry login from Docker's credentials store.
	For some providers an API key needs to be specified
	via env var or Docker config file.

	Alternatively credentials can be set as environment
	variables. Environment variables take precedence over
	the Docker credentials store.

	Environment variables can be specified with or without
	a server name. The variant without a server name takes
	precedence:

	 - DOCKER_USER and DOCKER_PASS
	 - DOCKER_USER__<SERVER>_<DOMAIN> and DOCKER_PASS__<SERVER>_<DOMAIN>
	   (example for server 'docker.io': DOCKER_USER__DOCKER_IO=my-user
	   and DOCKER_PASS__DOCKER_IO=my-password)

	The provider 'quay' needs an additional env var for the API key
	in form of APIKEY__<SERVERNAME>_<DOMAIN>=<apikey>.


	Dockerhub
	---------
	run 'docker login'

	(use password or Personal Access Token (PAT) with 'admin' scope)


	quay
	----
	- get an api key (bearer token) from the quay webinterface

	  - option 1: env var DOCKER_APIKEY=<apikey>
	    or env var APIKEY__<SERVERNAME>_<DOMAIN>=<apikey>
		(example for quay.io: 'export APIKEY__QUAY_IO=myapikey')

	  - option 2: in the Docker config file (default: "$HOME/.docker/config.json")
		add the json key 'plugins.docker-pushrm.apikey_<servername>' with the
		apikey as string value (example for quay.io: key name
		'plugins.docker-pushrm.apikey_quay.io')

	Env var takes precedence.


	harbor
	------
	run 'docker login <servername>' (example: 'docker login demo.goharbor.io')



	Creating a README file
	=======================

	Valid names: 'README.md' or 'README-containers.md'.

	'README-containers.md' takes precedence. This allows to set up a
	different README file for the container registry then for the git repo.

	The README file needs to be in the current working directory from which
	docker pushrm is being called.

	It's also possible to specify a path to a README file with
	'--file <path>' ('-f <path>').


	Optional [:TAG] argument
	========================

	The [:TAG] argument is optional and has no effect for the currently
	supported providers (which only support a README per repo, not per
	tag). It is in place in case that additional providers get added in
	the future that support READMEs on the tag level.


	Supported environment variables
	===============================
	
	DOCKER_USER, DOCKER_PASS, DOCKER_APIKEY, APIKEY__<SERVER>_<DOMAIN>,
	PUSHRM_PROVIDER, PUSHRM_SHORT, PUSHRM_FILE, PUSHRM_DEBUG, PUSHRM_CONFIG,
	PUSHRM_TARGET

	Commandline parameters take precedence over environment variables.
	Login environment variables take precedence over the local credentials
	store.



`,
	RunE: func(cmd *cobra.Command, args []string) error {
		if err := run(args); err != nil {
			return err
		}
		return nil
	},
}

func run(args []string) error {
	pushrmProvider := viper.GetString("provider")
	pushrmFile := viper.GetString("file")
	pushrmShortDesc := viper.GetString("short")

	log.Debug("subcommand \"pushrm\" called")

	//fmt.Println(os.Getenv("DOCKER_CLI_PLUGIN_ORIGINAL_CLI_COMMAND"))

	// lowest common ground: 100 runes (not bytes) on Dockerhub
	// this check is intentially global (not per provider) to
	// make cmd calls portable between providers without surprises
	if utf8.RuneCountInString(pushrmShortDesc) > 100 {
		log.Error("Short description is too long (max 100 characters)")
		os.Exit(1)
	}

	// our only positional argument: <servername>/<namespacename>/<reponame>:<tag> (servername + tag are optional)
	targetinfo := os.Getenv("PUSHRM_TARGET")
	if len(args) > 0 {
		if target := args[0]; target != "" {
			targetinfo = target
		}
	}

	if targetinfo == "" {
		return (errors.New("Missing [IMAGE] argument. Example: docker.io/mynamespace/myrepo:latest"))
		//log.Error("Missing [IMAGE] argument. Example: docker.io/mynamespace/myrepo:latest")
		//os.Exit(1)
	}

	// fail if namespacename is missing
	if len(strings.Split(targetinfo, "/")) < 2 {
		log.Error("Invalid [IMAGE] argument - missing namespace. Example: docker.io/mynamespace/myrepo:latest")
		os.Exit(1)
	}
	// fill up default servername, if missing
	if len(strings.Split(targetinfo, "/")) < 3 {
		targetinfo = "docker.io/" + targetinfo
	}
	// fill up default tagname, if missing
	if strings.Contains(targetinfo, ":") != true {
		targetinfo = targetinfo + ":latest"
	}
	log.Debug("Using target: ", targetinfo)

	var erro error

	if pushrmFile == "" {
		pushrmFile, erro = util.FindReadmeFile()
		if erro != nil {
			log.Error(erro)
			os.Exit(1)
		}
	}

	log.Debug("using README file: " + pushrmFile)

	readme, erro := util.ReadFile(pushrmFile)
	if erro != nil {
		log.Error(erro)
		os.Exit(1)
	}

	if (len(strings.Split(targetinfo, "/")) != 3) || (len(strings.Split(strings.Split(targetinfo, "/")[2], ":")) != 2) {
		log.Error("Invalid [IMAGE] argument - too many separators. Example: docker.io/mynamespace/myrepo:latest")
		os.Exit(1)
	}

	servername := strings.ToLower(strings.Split(targetinfo, "/")[0])
	namespacename := strings.Split(targetinfo, "/")[1]
	reponame := strings.Split(strings.Split(targetinfo, "/")[2], ":")[0]
	tagname := strings.Split(strings.Split(targetinfo, "/")[2], ":")[1]
	if servername == "docker.io" {
		pushrmProvider = "dockerhub"
	}
	if servername == "quay.io" {
		pushrmProvider = "quay"
	}
	log.Debug("server: ", servername)
	log.Debug("namespace: ", namespacename)
	log.Debug("repo: ", reponame)
	log.Debug("tag: ", tagname)
	log.Debug("repo provider: ", pushrmProvider)

	for _, e := range []string{namespacename, reponame, tagname, servername} {
		// yes, dots are allowed in all these fields
		if regexp.MustCompile(`^[0-9a-zA-Z\-_.]+$`).MatchString(e) == false {
			log.Error("Invalid [IMAGE argument] - bad characters or empty value. Example: docker.io/mynamespace/myrepo:latest")
			os.Exit(1)
		}
	}

	if pushrmProvider == "dockerhub" && servername != "docker.io" {
		log.Error("servername ", servername, " is not valid for provider ", pushrmProvider, " (try \"docker.io\")")
		os.Exit(1)
	}

	var prov provider.Provider

	switch pushrmProvider {
	case "dockerhub":
		prov = dockerhub.Dockerhub{}
	case "quay":
		prov = quay.Quay{}
	case "harbor2":
		prov = harbor2.Harbor2{}
	default:
		log.Error("unsupported repo provider: ", pushrmProvider+". See \"--help\" for supported providers. ")
		os.Exit(1)
	}

	authident := prov.GetAuthident()
	var authidentIsFuzzy bool
	authidentIsFuzzy = false

	if authident == "__SERVERNAME__" {
		authident = servername
		authidentIsFuzzy = true
	}

	var dockerUser string
	var dockerPasswd string
	var err error

	// generic env var (no servername specified) takes precedence
	dockerUser = os.Getenv("DOCKER_USER")
	dockerPasswd = os.Getenv("DOCKER_PASS")
	if dockerUser != "" && dockerPasswd != "" {
		log.Debug("using credentials for user " + dockerUser + " from generic env var")
	}

	// env var with servername is next
	if dockerUser == "" || dockerPasswd == "" {
		suffix := strings.ToUpper(strings.Replace(servername, ".", "_", -1))
		dockerUser = os.Getenv("DOCKER_USER__" + suffix)
		dockerPasswd = os.Getenv("DOCKER_PASS__" + suffix)
		if dockerUser != "" && dockerPasswd != "" {
			log.Debug("using credentials for user " + dockerUser + " from env var for suffix " + suffix)
		}
	}

	// if credentials are not found in env vars, look in the Docker credentials store
	if (dockerUser == "" || dockerPasswd == "") && authident != "__NONE__" {
		log.Debug("no credentials found in env vars. Trying Docker credentials store")
		log.Debug("Using config file: ", viper.ConfigFileUsed())

		if viper.ConfigFileUsed() == "" {
			log.Error("Docker config file not found. Run \"docker login\" first to create it. ")
			os.Exit(1)
		}

		// a provider can request to handle auth itself with authident __NONE__
		if authident != "__NONE__" {
			dockerUser, dockerPasswd, err = util.GetDockerCreds(authident, authidentIsFuzzy)
			if err != nil {
				log.Error(err)
				os.Exit(1)
			}
		} else {
			dockerUser = ""
			dockerPasswd = ""
		}
	}

	//log.Debug("Using Docker creds: ", dockerUser, " ", dockerPasswd)
	log.Debug("Using Docker creds: ", dockerUser, " ", "********")

	err = prov.Pushrm(servername, namespacename, reponame, tagname, dockerUser, dockerPasswd, readme, pushrmShortDesc)
	if err != nil {
		log.Error(err)
		os.Exit(1)
	}

	return nil

	// ---------
}

func init() {

	const usageTemplate = `
Usage:{{if .Runnable}}
  {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
  {{.CommandPath}} [command]{{end}}

Flags:
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{if .HasAvailableInheritedFlags}}

Global Flags:
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}
  `

	const helpTemplate = `
{{with (or .Long .Short)}}{{. | trimTrailingWhitespaces}}
{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}
  `

	rootCmd.AddCommand(pushrmCmd)
	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// pushrmCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// pushrmCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
	pushrmCmd.Flags().StringVarP(&providername, "provider", "p", "dockerhub", "repo type: dockerhub, harbor2, quay")
	pushrmCmd.Flags().StringVarP(&rfile, "file", "f", "", "README file (defaults: \"./README-containers.md\", \"./README.md\")")
	pushrmCmd.Flags().StringVarP(&shortdesc, "short", "s", "", "short description (optional)")
	pushrmCmd.Parent().SetUsageTemplate(usageTemplate)
	pushrmCmd.Parent().SetHelpTemplate(helpTemplate)

	viper.BindPFlag("provider", pushrmCmd.Flags().Lookup("provider"))
	viper.BindPFlag("file", pushrmCmd.Flags().Lookup("file"))
	viper.BindPFlag("short", pushrmCmd.Flags().Lookup("short"))
}


================================================
FILE: cmd/root.go
================================================
/*
Copyright © 2020 Christian Korneck <christian@korneck.de>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

package cmd

import (
	"fmt"
	"os"
	"path/filepath"

	log "github.com/sirupsen/logrus"

	"github.com/spf13/cobra"

	homedir "github.com/mitchellh/go-homedir"
	"github.com/spf13/viper"
)

var cfgFile string
var isDebug bool

var dockerGlobalConfig string
var dockerGlobalContext string
var dockerGlobalHost string
var dockerGlobalTlscacert string
var dockerGlobalLoglevel string
var dockerGlobalTlscert string
var dockerGlobalTlskey string

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
	Use:   "docker-pushrm",
	Short: "push README file from current working directory to container registry (Dockerhub, quay, harbor2)",
	Long: `push README file from current working directory to container registry (Dockerhub, quay, harbor2)
`,
	// Uncomment the following line if your bare application
	// has an action associated with it:
	//	Run: func(cmd *cobra.Command, args []string) { },
}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
	if err := rootCmd.Execute(); err != nil {
		//at this point we don't have logrus yet, using print instead
		fmt.Println(err)
		os.Exit(1)
	}
}

func init() {

	cobra.OnInitialize(initConfig)

	// Here you will define your flags and configuration settings.
	// Cobra supports persistent flags, which, if defined here,
	// will be global for your application.

	rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default \"$HOME/.docker/config.json\")")
	rootCmd.PersistentFlags().BoolVarP(&isDebug, "debug", "D", false, "Enable debug mode")

	// these are the docker cli global flags
	// (we define them here so that our plugin doesn't break if they're set, but don't do anything with em)
	rootCmd.PersistentFlags().StringVarP(&dockerGlobalContext, "context", "c", "", "(not supported)")
	rootCmd.PersistentFlags().StringVarP(&dockerGlobalHost, "host", "H", "", "(not supported)")
	rootCmd.PersistentFlags().StringVarP(&dockerGlobalLoglevel, "log-level", "l", "", "(not supported)")
	rootCmd.PersistentFlags().Bool("tls", true, "(not supported)")
	rootCmd.PersistentFlags().Bool("tlsverify", true, "(not supported)")
	rootCmd.PersistentFlags().StringVar(&dockerGlobalTlscacert, "tlscacert", "", "(not supported)")
	rootCmd.PersistentFlags().StringVar(&dockerGlobalTlscert, "tlscert", "", "(not supported)")
	rootCmd.PersistentFlags().StringVar(&dockerGlobalTlskey, "tlskey", "", "(not supported)")

	// hide unsupported flags so that they don't show up with `docker-pushrm pushrm --help`
	rootCmd.PersistentFlags().MarkHidden("context")
	rootCmd.PersistentFlags().MarkHidden("host")
	rootCmd.PersistentFlags().MarkHidden("log-level")
	rootCmd.PersistentFlags().MarkHidden("tls")
	rootCmd.PersistentFlags().MarkHidden("tlsverify")
	rootCmd.PersistentFlags().MarkHidden("tlscacert")
	rootCmd.PersistentFlags().MarkHidden("tlscert")
	rootCmd.PersistentFlags().MarkHidden("tlskey")

	viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config"))
	viper.BindPFlag("debug", rootCmd.PersistentFlags().Lookup("debug"))

	// Cobra also supports local flags, which will only run
	// when this action is called directly.
	// rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")

}

// initConfig reads in config file and ENV variables if set.
func initConfig() {
	viper.AutomaticEnv() // read in environment variables that match
	viper.SetEnvPrefix("pushrm")

	pushrmConfig := viper.GetString("config")
	pushrmDebug := viper.GetBool("debug")

	if pushrmDebug {
		log.SetLevel(log.DebugLevel)
	} else {
		log.SetLevel(log.WarnLevel)
	}
	log.SetFormatter(&log.TextFormatter{DisableTimestamp: true})

	log.Debug("root cmd init config")

	if pushrmConfig != "" {
		// Use config file from the flag.
		viper.SetConfigFile(pushrmConfig)
	} else {
		// Find home directory.
		home, err := homedir.Dir()
		if err != nil {
			log.Debug(err)
			log.Error("can't find home dir / Docker config file")
			os.Exit(1)
		}
		log.Debug("home dir: ", home)

		// Search config in home directory with name ".docker-pushrm" (without extension).
		viper.AddConfigPath(filepath.Join(home, "/.docker"))

		viper.SetConfigName("config") //filename without .json extension
	}

	// If a config file is found, read it in.
	if err := viper.ReadInConfig(); err == nil {
	}

}


================================================
FILE: go.mod
================================================
module github.com/christian-korneck/docker-pushrm

go 1.16

require (
	github.com/mitchellh/go-homedir v1.1.0
	github.com/sirupsen/logrus v1.8.1
	github.com/spf13/cobra v1.2.1
	github.com/spf13/viper v1.8.1
	golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
	golang.org/x/text v0.3.6 // indirect
)


================================================
FILE: go.sum
================================================
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=


================================================
FILE: gon.json
================================================
{
    "source" : ["./docker-pushrm"],
    "bundle_id" : "de.korneck.christian.docker-pushrm",
    "apple_id": {
        "password":  "@env:AC_PASSWORD"
    },
    "sign" :{
        "application_identity" : "Developer ID Application: Christian Korneck"
    },
    "zip" :{
        "output_path" : "./docker-pushrm.zip"
    }
}

================================================
FILE: main.go
================================================
/*
Copyright © 2020 Christian Korneck <christian@korneck.de>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

package main

import (
	"os"

	"github.com/christian-korneck/docker-pushrm/cmd"
	"github.com/christian-korneck/docker-pushrm/util"
)

func main() {

	// if called as a standalone tool (not as a docker cli plugin), proxy directly to the `pushrm` subcommand
	if (os.Getenv("DOCKER_CLI_PLUGIN_ORIGINAL_CLI_COMMAND") == "") && (util.StringInSlice("docker-cli-plugin-metadata", os.Args) == false) {

		newArgs := make([]string, (len(os.Args) + 1))

		newArgs[0] = os.Args[0]
		newArgs[1] = "pushrm"
		for i := 2; i <= len(os.Args); i++ {
			newArgs[i] = os.Args[i-1]
		}
		os.Args = newArgs

	}

	// Cobra, übernehmen Sie
	cmd.Execute()
}


================================================
FILE: provider/dockerhub/dockerhub.go
================================================
/*
Copyright © 2020 Christian Korneck <christian@korneck.de>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

package dockerhub

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"

	"github.com/christian-korneck/docker-pushrm/util"
	log "github.com/sirupsen/logrus"
)

//Dockerhub struct
type Dockerhub struct {
}

//Pushrm is the main provider function
func (f Dockerhub) Pushrm(servername string, namespacename string, reponame string, tagname string, dockerUser string, dockerPasswd string, readme string, shortdesc string) error {

	log.Debug("Dockerhub.Pushrm called")
	jwt, err := GetJwt(dockerUser, dockerPasswd)
	if err != nil {
		log.Debug(err)
		return fmt.Errorf("error trying to get a JWT token from Dockerhub for the stored Docker login. Try \"docker logout\" and \"docker login\". Also, if you have 2FA auth enabled in Dockerhub you'll need to disable it for this tool to work. (This is an unfortunate Dockerhub limitation, see docs for more infos). ")
	}
	err = PatchDescription(jwt, readme, namespacename, reponame, shortdesc)
	if err != nil {
		log.Debug(err)
		return fmt.Errorf("error pushing readme to repo server. See error message below. Run with \"--debug\" for more details. \n\n" + err.Error())
	}

	return nil
}

//GetAuthident returns authident for local Docker credentials store
func (f Dockerhub) GetAuthident() (authident string) {
	log.Debug("Dockerhub.GetAuthident called")
	authident = "https://index.docker.io/v1/"
	return
}

//GetJwt Auth against Dockerhub with user/passwd and request a jwt token
func GetJwt(dockerUser string, dockerPasswd string) (jwt string, error error) {

	url := "https://hub.docker.com/v2/users/login/"
	method := "POST"

	payloadJSON, err := json.Marshal(map[string]string{"username": dockerUser, "password": dockerPasswd})
	if err != nil {
		log.Debug(err)
		return "", fmt.Errorf("error retrieving Dockerhub jwt token, error marshal payload")
	}

	payloadStr := util.BytesToString(payloadJSON)
	payload := strings.NewReader(payloadStr)

	client := &http.Client{}
	req, err := http.NewRequest(method, url, payload)

	if err != nil {
		log.Debug(err)
		return "", fmt.Errorf("error retrieving Dockerhub jwt token, error creating http request")
	}
	req.Header.Add("Content-Type", "application/json")

	res, err := client.Do(req)
	if err != nil {
		log.Debug(err)
		return "", fmt.Errorf("error retrieving Dockerhub jwt token, error making http request")
	}

	log.Debug("retrieve Dockerhub jwt token, status code: ", res.StatusCode)

	if res.StatusCode != 200 {
		return "", fmt.Errorf("error retrieving Dockerhub jwt token, bad status code for response: " + res.Status)

	}

	defer res.Body.Close()
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Debug(err)
		return "", fmt.Errorf("error retrieving Dockerhub jwt token, error reading response body")
	}

	var dat map[string]interface{}
	if err := json.Unmarshal(body, &dat); err != nil {
		log.Debug(err)
		return "", fmt.Errorf("error retrieving Dockerhub jwt token, error parsing json")
	}

	if dat["token"] == nil || dat["token"].(string) == "" {
		return "", fmt.Errorf("error retrieving Dockerhub jwt token, no jtw token received")
	}

	return dat["token"].(string), nil
}

//PatchDescription - api call to update the repo description
func PatchDescription(jwt string, readme string, namespacename string, reponame string, shortdesc string) (error error) {

	// trailing slash is crucial
	apiurl := "https://hub.docker.com/v2/repositories/" + namespacename + "/" + reponame + "/"
	method := "PATCH"

	bodydata := make(map[string]string)
	bodydata["full_description"] = readme
	if shortdesc != "" {
		bodydata["description"] = shortdesc
	}
	jsonbody, _ := json.Marshal(bodydata)

	payload := strings.NewReader(string(jsonbody))
	client := &http.Client{}
	req, err := http.NewRequest(method, apiurl, payload)
	if err != nil {
		log.Debug(err)
		return fmt.Errorf("error pushing README, error creating http request")
	}

	req.Header.Add("Authorization", "JWT "+jwt)
	req.Header.Add("Content-Type", "application/json")

	res, err := client.Do(req)
	if err != nil {
		log.Debug(err)
		return fmt.Errorf("error pushing README, error creating http request")
	}

	defer res.Body.Close()
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Debug(err)
		return fmt.Errorf("error pushing README, error reading response body")
	}

	log.Debug("push readme, response body: ", string(body))

	var dat map[string]interface{}
	if err := json.Unmarshal(body, &dat); err != nil {
		log.Debug(err)
		return fmt.Errorf("error pushing README, error parsing returned json")
	}

	log.Debug("push README, status code: ", res.StatusCode)

	if res.StatusCode != 200 {
		msg := "error pushing README, bad status code for response: " + res.Status
		if dat["detail"] != nil {
			msg = msg + ". Server responded: \"" + dat["detail"].(string) + "\""
		}
		if res.StatusCode == 403 {
			msg = msg + ". Try \"docker logout\" and \"docker login\". If you use a PAT token make sure it has sufficient privileges (\"admin\" scope)."

		}
		return fmt.Errorf(msg)

	}

	if dat["full_description"] != readme {
		return fmt.Errorf("error pushing README, pushed readme to repo server but validation failed")
	}

	if shortdesc != "" && dat["description"] != shortdesc {
		return fmt.Errorf("error setting Short Description, pushed to repo server but validation failed")
	}

	log.Debug("content validation successfull, readme successfully pushed to repo server")
	return nil

}


================================================
FILE: provider/harbor2/harbor2.go
================================================
/*
Copyright © 2020 Christian Korneck <christian@korneck.de>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

package harbor2

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"

	log "github.com/sirupsen/logrus"
)

//Harbor2 struct
type Harbor2 struct {
}

//Pushrm is the main provider function
func (f Harbor2) Pushrm(servername string, namespacename string, reponame string, tagname string, dockerUser string, dockerPasswd string, readme string, shortdesc string) error {

	if shortdesc != "" {
		log.Warn("Short description not supported for provider \"harbor2\". Ignoring.")
	}

	log.Debug("Harbor2.Pushrm called")

	err := PatchDescription(dockerUser, dockerPasswd, readme, servername, namespacename, reponame)
	if err != nil {
		log.Debug(err)
		return fmt.Errorf("error pushing readme to repo server. See error message below. Run with \"--debug\" for more details. \n\n" + err.Error())
	}

	return nil
}

//GetAuthident returns authident for local Docker credentials store
func (f Harbor2) GetAuthident() (authident string) {
	log.Debug("Harbor2.GetAuthident called")
	authident = "__SERVERNAME__"
	return
}

//PatchDescription - api call to update the repo description
func PatchDescription(dockerUser string, dockerPasswd string, readme string, servername string, namespacename string, reponame string) (error error) {

	apiurl := "https://" + servername + "/api/v2.0/projects/" + namespacename + "/repositories/" + reponame
	method := "PUT"

	jsonbody, _ := json.Marshal(map[string]string{"description": readme})
	payload := strings.NewReader(string(jsonbody))

	client := &http.Client{}
	req, err := http.NewRequest(method, apiurl, payload)
	if err != nil {
		log.Debug(err)
		return fmt.Errorf("error pushing README, error creating http request")
	}

	creds := base64.StdEncoding.EncodeToString([]byte(dockerUser + ":" + dockerPasswd))
	req.Header.Add("Authorization", "Basic "+creds)
	req.Header.Add("Content-Type", "application/json")

	res, err := client.Do(req)
	if err != nil {
		log.Debug(err)
		return fmt.Errorf("error pushing README, error creating http request")
	}

	defer res.Body.Close()
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Debug(err)
		return fmt.Errorf("error pushing README, error reading response body")
	}

	log.Debug("push readme, response body: " + string(body))

	var dat map[string]interface{}
	// json only returned in case of failure for error message, otherwise empty
	if res.StatusCode != 200 {
		if err := json.Unmarshal(body, &dat); err != nil {
			log.Debug(err)
		}
	}

	log.Debug("push README, status code: ", res.StatusCode)

	if res.StatusCode != 200 {
		msg := "error pushing README, bad status code for response: " + res.Status
		if dat["errors"] != nil {

			firsterror := dat["errors"].([]interface{})[0].(map[string]interface{})
			msg = msg + ". Server responded: \"" + firsterror["code"].(string) + " - " + firsterror["message"].(string) + "\""
		}
		if res.StatusCode == 403 {
			msg = msg + ". Try \"docker logout\" and \"docker login\". "

		}
		return fmt.Errorf(msg)

	} else {
		log.Debug("status code OK, readme successfully pushed to repo server")
		return nil
	}

}


================================================
FILE: provider/provider/provider.go
================================================
/*
Copyright © 2020 Christian Korneck <christian@korneck.de>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

package provider

//Provider interface
type Provider interface {
	//GetAuthident - returns the key name under which the provider credentials are stored in Docker's credentials store. Special values: __SERVERNAME__ = use servername, __NONE__ = retrieving credentials will be handled by the provider
	GetAuthident() (authident string)
	//Pushrm function - main provider function, performs the api  call to update the repo description
	Pushrm(servername string, namespacename string, reponame string, tagname string, dockerUser string, dockerPasswd string, readme string, shortdesc string) error
}


================================================
FILE: provider/quay/quay.go
================================================
/*
Copyright © 2020 Christian Korneck <christian@korneck.de>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

package quay

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"

	"github.com/christian-korneck/docker-pushrm/util"
	log "github.com/sirupsen/logrus"
)

//Quay struct
type Quay struct {
}

//Pushrm is the main provider function
func (f Quay) Pushrm(servername string, namespacename string, reponame string, tagname string, dockerUser string, dockerPasswd string, readme string, shortdesc string) error {

	if shortdesc != "" {
		log.Warn("Short description not supported for provider \"quay\". Ignoring.")
	}

	log.Debug("Quay.Pushrm called")

	apikey, err := util.GetApikey(servername)
	if err != nil {
		return fmt.Errorf(err.Error())
	}
	//log.Debug("apikey: " + apikey)
	log.Debug("apikey: " + "********")

	err = PatchDescription(apikey, readme, servername, namespacename, reponame)
	if err != nil {
		log.Debug(err)
		return fmt.Errorf("error pushing readme to repo server. See error message below. Run with \"--debug\" for more details. \n\n" + err.Error())
	}

	return nil
}

//GetAuthident returns authident for local Docker credentials store
func (f Quay) GetAuthident() (authident string) {
	log.Debug("Quay.GetAuthident called")
	authident = "__NONE__"
	return
}

//PatchDescription - api call to update the repo description
func PatchDescription(quaytoken string, readme string, servername string, namespacename string, reponame string) (error error) {

	apiurl := "https://" + servername + "/api/v1/repository/" + namespacename + "/" + reponame
	method := "PUT"

	jsonbody, _ := json.Marshal(map[string]string{"description": readme})
	payload := strings.NewReader(string(jsonbody))

	client := &http.Client{}
	req, err := http.NewRequest(method, apiurl, payload)
	if err != nil {
		log.Debug(err)
		return fmt.Errorf("error pushing README, error creating http request")
	}

	req.Header.Add("Authorization", "Bearer "+quaytoken)
	req.Header.Add("Content-Type", "application/json")

	res, err := client.Do(req)
	if err != nil {
		log.Debug(err)
		return fmt.Errorf("error pushing README, error creating http request")
	}

	defer res.Body.Close()
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Debug(err)
		return fmt.Errorf("error pushing README, error reading response body")
	}

	log.Debug("push readme, response body: " + string(body))

	var dat map[string]interface{}
	// we need the json response only in case of failure to get the error message
	if res.StatusCode != 200 {
		if err := json.Unmarshal(body, &dat); err != nil {
			log.Debug(err)
		}
	}

	log.Debug("push README, status code: ", res.StatusCode)

	if res.StatusCode != 200 {
		msg := "error pushing README, bad status code for response: " + res.Status
		if dat["error_message"] != nil {

			msg = msg + ". Server responded: \"" + dat["error_message"].(string) + "\""
		}
		if res.StatusCode == 403 {
			msg = msg + ". Try \"docker logout\" and \"docker login\""

		}
		return fmt.Errorf(msg)

	} else {
		log.Debug("status code OK, readme successfully pushed to repo server")
		return nil
	}

}


================================================
FILE: util/util.go
================================================
/*
Copyright © 2020 Christian Korneck <christian@korneck.de>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

package util

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	log "github.com/sirupsen/logrus"

	"github.com/spf13/viper"
)

// BytesToString converts
func BytesToString(b []byte) string {
	return string(b[:])
}

// StringInSlice checks if a string exists in a slice
func StringInSlice(checkval string, list []string) bool {
	for _, b := range list {
		if b == checkval {
			return true
		}
	}
	return false
}

// ReadFile reads a file
func ReadFile(path string) (filecontent string, error error) {
	content, err := ioutil.ReadFile(path)
	if err != nil {
		log.Debug(err)
		return "", fmt.Errorf("could not read README file: " + path)
	}
	text := string(content)
	return text, nil
}

//GetApikey retrieves an API key from env var or the local Docker config file
func GetApikey(servername string) (apikey string, error error) {
	log.Debug("util.GetApikey called")

	genericEnvval := os.Getenv("DOCKER_APIKEY")
	if genericEnvval != "" {
		return genericEnvval, nil
	}

	envkey := "APIKEY__" + strings.ToUpper(strings.Replace(servername, ".", "_", -1))
	querykey := "plugins.docker-pushrm.apikey_" + servername

	envval := os.Getenv(envkey)

	if envval != "" {
		apikey = envval
		return apikey, nil
	} else {
		cfgval := viper.GetString(querykey)
		if cfgval != "" {
			apikey = cfgval
			return apikey, nil
		} else {
			return "", fmt.Errorf("could not find api key for server " + servername + ". Either specify env var DOCKER_APIKEY or env var " + envkey + " or " + querykey + " in the local Docker config file. ")
		}

	}

}

//GetDockerCreds retrieves credentials from the Docker creds store
func GetDockerCreds(authident string, authidentIsFuzzy bool) (dockerUser string, dockerPasswd string, error error) {

	var candidates []string
	if authidentIsFuzzy == true {
		candidates = []string{authident, ("https://" + authident), ("https://" + authident + "/"), ("http://" + authident), ("http://" + authident + "/")}
	} else {
		candidates = []string{authident}
	}
	for _, candidate := range candidates {
		dockerUser, dockerPasswd = "", ""
		dockerUser, dockerPasswd, err := QueryDockerCreds(candidate)
		if err != nil {
			log.Debug("tried candidate " + candidate + ", got error: " + err.Error())
		}

		if dockerUser == "" && dockerPasswd == "" {
			log.Debug("tried candidate " + candidate + ": could not find credentials")
		} else {
			log.Debug("tried candidate " + candidate + ": found credentials for user " + dockerUser)
			return dockerUser, dockerPasswd, nil
		}

	}

	return "", "", fmt.Errorf("no Docker credentials found for this server/provider. Run 'docker login' first. ")

}

//QueryDockerCreds fetches credentials for an authid
func QueryDockerCreds(authident string) (dockerUser string, dockerPasswd string, error error) {

	log.Debug("util.GetDockerCreds called")

	if viper.GetString("auths."+authident+".auth") != "" {

		credsb64 := viper.GetString("auths." + authident + ".auth")
		credsclearb, err := base64.StdEncoding.DecodeString(credsb64)
		if err != nil {
			log.Debug(err)
			return "", "", fmt.Errorf("Error parsing auth info from the Docker config file. Check your local Docker config. ")
		}
		credsclear := string(credsclearb)
		credssplit := strings.Split(credsclear, ":")
		dockerUser = credssplit[0]
		dockerPasswd = credsclear[len(dockerUser)+1:]

	} else {
		if viper.GetString("credsStore") != "" {
			executable := "docker-credential-" + viper.GetString("credsStore")
			if viper.GetString("credsStore") == "wincred" {
				executable = executable + ".exe"
			}
			shx := exec.Command(executable, "get")
			stdin, err := shx.StdinPipe()
			if err != nil {
				log.Debug(err)
				return "", "", fmt.Errorf("Error executing the Docker credentials helper. Check your local Docker config and/or installation. ")
			}

			done := make(chan bool)

			go func() {
				defer func() { done <- true }()
				defer stdin.Close()
				io.WriteString(stdin, authident)

			}()

			<-done

			out, err := shx.CombinedOutput()
			if err != nil {
				log.Debug(err)
				return "", "", fmt.Errorf("no Docker credentials found for this server/provider. Run 'docker login' first. ")
			}

			var dat map[string]interface{}
			if err := json.Unmarshal(out, &dat); err != nil {
				log.Debug(err)
				return "", "", fmt.Errorf("Error parsing credentials from Docker creds provider. Run 'docker login' first. ")
			}
			dockerUser = dat["Username"].(string)
			dockerPasswd = dat["Secret"].(string)

		} else {
			return "", "", fmt.Errorf("no Docker credentials found for this server/provider. Run 'docker login' first. ")
		}
	}

	return dockerUser, dockerPasswd, nil
}

//FindReadmeFile trys to find a readme file in the cwd
func FindReadmeFile() (foundfile string, error error) {
	preferedfilenames := []string{"./README-containers.md", "./README.md"} //prefer these filenames in this order

	for _, preferedfilename := range preferedfilenames {
		matches, err := filepath.Glob(preferedfilename)
		if err != nil {
			log.Debug(err)
			return "", fmt.Errorf("error while searching for default readme file")
		}
		if len(matches) >= 1 {
			foundfile = preferedfilename
			break
		}
	}

	if foundfile == "" {
		matches, err := filepath.Glob("./[R|r][E|e][A|a][D|d][M|m][E|e]*")
		if err != nil {
			log.Debug(err)
			return "", fmt.Errorf("error while searching for alternate readme file")
		}
		if len(matches) < 1 {
			return "", fmt.Errorf("README file not found in the current working directory. Create a file \"README-containers.md\" or \"README.md\" or \"cd\" into a directory that contains a README file. ")
		} else {
			foundfile = matches[0]
		}
	}

	return foundfile, nil
}
Download .txt
gitextract_xhlei939/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   └── problem-or-bug.md
│   └── workflows/
│       ├── release.yml
│       └── webhook_target.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── README-containers.md
├── README.md
├── cmd/
│   ├── docker-cli-plugin-metadata.go
│   ├── pushrm.go
│   └── root.go
├── go.mod
├── go.sum
├── gon.json
├── main.go
├── provider/
│   ├── dockerhub/
│   │   └── dockerhub.go
│   ├── harbor2/
│   │   └── harbor2.go
│   ├── provider/
│   │   └── provider.go
│   └── quay/
│       └── quay.go
└── util/
    └── util.go
Download .txt
SYMBOL INDEX (28 symbols across 9 files)

FILE: cmd/docker-cli-plugin-metadata.go
  function init (line 48) | func init() {

FILE: cmd/pushrm.go
  function run (line 185) | func run(args []string) error {
  function init (line 364) | func init() {

FILE: cmd/root.go
  function Execute (line 62) | func Execute() {
  function init (line 70) | func init() {
  function initConfig (line 112) | func initConfig() {

FILE: main.go
  function main (line 32) | func main() {

FILE: provider/dockerhub/dockerhub.go
  type Dockerhub (line 37) | type Dockerhub struct
    method Pushrm (line 41) | func (f Dockerhub) Pushrm(servername string, namespacename string, rep...
    method GetAuthident (line 59) | func (f Dockerhub) GetAuthident() (authident string) {
  function GetJwt (line 66) | func GetJwt(dockerUser string, dockerPasswd string) (jwt string, error e...
  function PatchDescription (line 123) | func PatchDescription(jwt string, readme string, namespacename string, r...

FILE: provider/harbor2/harbor2.go
  type Harbor2 (line 37) | type Harbor2 struct
    method Pushrm (line 41) | func (f Harbor2) Pushrm(servername string, namespacename string, repon...
    method GetAuthident (line 59) | func (f Harbor2) GetAuthident() (authident string) {
  function PatchDescription (line 66) | func PatchDescription(dockerUser string, dockerPasswd string, readme str...

FILE: provider/provider/provider.go
  type Provider (line 26) | type Provider interface

FILE: provider/quay/quay.go
  type Quay (line 37) | type Quay struct
    method Pushrm (line 41) | func (f Quay) Pushrm(servername string, namespacename string, reponame...
    method GetAuthident (line 66) | func (f Quay) GetAuthident() (authident string) {
  function PatchDescription (line 73) | func PatchDescription(quaytoken string, readme string, servername string...

FILE: util/util.go
  function BytesToString (line 42) | func BytesToString(b []byte) string {
  function StringInSlice (line 47) | func StringInSlice(checkval string, list []string) bool {
  function ReadFile (line 57) | func ReadFile(path string) (filecontent string, error error) {
  function GetApikey (line 68) | func GetApikey(servername string) (apikey string, error error) {
  function GetDockerCreds (line 98) | func GetDockerCreds(authident string, authidentIsFuzzy bool) (dockerUser...
  function QueryDockerCreds (line 127) | func QueryDockerCreds(authident string) (dockerUser string, dockerPasswd...
  function FindReadmeFile (line 191) | func FindReadmeFile() (foundfile string, error error) {
Condensed preview — 20 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (141K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE/problem-or-bug.md",
    "chars": 996,
    "preview": "---\nname: problem or bug\nabout: I have a problem with docker-pushrm\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe "
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 12571,
    "preview": "name: Release\non:\n  push:\n    # Sequence of patterns matched against refs/tags\n    tags:\n      - 'v*' # Push events to m"
  },
  {
    "path": ".github/workflows/webhook_target.yml",
    "chars": 470,
    "preview": "name: delete calling job's artifacts\non: repository_dispatch\njobs:\n  main:\n    runs-on: ubuntu-latest\n    steps:\n    - n"
  },
  {
    "path": ".gitignore",
    "chars": 284,
    "preview": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n/docker-pushrm\n\n# Test binary, built with `go test -"
  },
  {
    "path": "Dockerfile",
    "chars": 563,
    "preview": "FROM golang:1.16-alpine AS builder\nWORKDIR /go/src/github.com/christian-korneck/docker-pushrm\nCOPY . .\nRUN apk add --no-"
  },
  {
    "path": "LICENSE",
    "chars": 1074,
    "preview": "MIT License\n\nCopyright (c) 2020 Christian Korneck\n\nPermission is hereby granted, free of charge, to any person obtaining"
  },
  {
    "path": "README-containers.md",
    "chars": 3854,
    "preview": "# Docker Push Readme\n\nThis is a container image of [`docker-pushrm`](https://github.com/christian-korneck/docker-pushrm)"
  },
  {
    "path": "README.md",
    "chars": 9926,
    "preview": "# Docker Push Readme\n\nUpdate the README of your container repo on Dockerhub, Quay or Harbor with a simple Docker command"
  },
  {
    "path": "cmd/docker-cli-plugin-metadata.go",
    "chars": 2389,
    "preview": "/*\nCopyright © 2020 Christian Korneck <christian@korneck.de>\n\nPermission is hereby granted, free of charge, to any perso"
  },
  {
    "path": "cmd/pushrm.go",
    "chars": 12500,
    "preview": "/*\nCopyright © 2020 Christian Korneck <christian@korneck.de>\n\nPermission is hereby granted, free of charge, to any perso"
  },
  {
    "path": "cmd/root.go",
    "chars": 5494,
    "preview": "/*\nCopyright © 2020 Christian Korneck <christian@korneck.de>\n\nPermission is hereby granted, free of charge, to any perso"
  },
  {
    "path": "go.mod",
    "chars": 312,
    "preview": "module github.com/christian-korneck/docker-pushrm\n\ngo 1.16\n\nrequire (\n\tgithub.com/mitchellh/go-homedir v1.1.0\n\tgithub.co"
  },
  {
    "path": "go.sum",
    "chars": 59373,
    "preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1"
  },
  {
    "path": "gon.json",
    "chars": 325,
    "preview": "{\n    \"source\" : [\"./docker-pushrm\"],\n    \"bundle_id\" : \"de.korneck.christian.docker-pushrm\",\n    \"apple_id\": {\n        "
  },
  {
    "path": "main.go",
    "chars": 1723,
    "preview": "/*\nCopyright © 2020 Christian Korneck <christian@korneck.de>\n\nPermission is hereby granted, free of charge, to any perso"
  },
  {
    "path": "provider/dockerhub/dockerhub.go",
    "chars": 6471,
    "preview": "/*\nCopyright © 2020 Christian Korneck <christian@korneck.de>\n\nPermission is hereby granted, free of charge, to any perso"
  },
  {
    "path": "provider/harbor2/harbor2.go",
    "chars": 4171,
    "preview": "/*\nCopyright © 2020 Christian Korneck <christian@korneck.de>\n\nPermission is hereby granted, free of charge, to any perso"
  },
  {
    "path": "provider/provider/provider.go",
    "chars": 1684,
    "preview": "/*\nCopyright © 2020 Christian Korneck <christian@korneck.de>\n\nPermission is hereby granted, free of charge, to any perso"
  },
  {
    "path": "provider/quay/quay.go",
    "chars": 4099,
    "preview": "/*\nCopyright © 2020 Christian Korneck <christian@korneck.de>\n\nPermission is hereby granted, free of charge, to any perso"
  },
  {
    "path": "util/util.go",
    "chars": 6765,
    "preview": "/*\nCopyright © 2020 Christian Korneck <christian@korneck.de>\n\nPermission is hereby granted, free of charge, to any perso"
  }
]

About this extraction

This page contains the full source code of the christian-korneck/docker-pushrm GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 20 files (131.9 KB), approximately 52.0k tokens, and a symbol index with 28 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!