├── .github └── workflows │ ├── distroless.yml │ ├── flannel.yml │ ├── google-samples.yml │ ├── helm.yml │ ├── istio.yml │ ├── kubeadm.yml │ ├── linkerd.yml │ └── spinnaker.yml ├── .gitignore ├── .golangci.yml ├── .logo.png ├── .travis-after-success.sh ├── .travis-before.sh ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── cmd ├── flannel.go ├── gcr.go ├── root.go └── sync.go ├── core ├── common.go ├── manifests.go ├── notification.go ├── synchronizer.go ├── synchronizer_flannel.go ├── synchronizer_gcr.go └── types.go ├── go.mod ├── go.sum ├── main.go └── version /.github/workflows/distroless.yml: -------------------------------------------------------------------------------- 1 | name: Sync Distroless 2 | 3 | on: 4 | push: 5 | branches: [ disabled ] 6 | pull_request: 7 | branches: [ disabled ] 8 | 9 | jobs: 10 | build: 11 | name: Build imgsync 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Set up Go 1.14 15 | uses: actions/setup-go@v2 16 | with: 17 | go-version: 1.14 18 | id: go 19 | - name: Check out code 20 | uses: actions/checkout@v2 21 | - name: Cache go mod 22 | uses: actions/cache@v1 23 | with: 24 | path: ~/go/pkg/mod 25 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 26 | restore-keys: | 27 | ${{ runner.os }}-go- 28 | - name: Build binary file 29 | run: make bin 30 | - name: Upload artifact 31 | uses: actions/upload-artifact@v1 32 | with: 33 | name: imgsync 34 | path: imgsync 35 | sync_distroless: 36 | name: Sync distroless 37 | needs: build 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Download artifact 41 | uses: actions/download-artifact@v1 42 | with: 43 | name: imgsync 44 | path: ./imgsync 45 | - name: Cache manifests 46 | uses: actions/cache@v1 47 | with: 48 | path: manifests 49 | key: distroless 50 | - name: Sync distroless images 51 | env: 52 | TZ: Asia/Shanghai 53 | DOCKER_USER: ${{ secrets.DOCKER_USER }} 54 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 55 | run: | 56 | chmod +x ./imgsync/imgsync 57 | ./imgsync/imgsync gcr --namespace distroless --user ${DOCKER_USER} --password ${DOCKER_PASSWORD} -------------------------------------------------------------------------------- /.github/workflows/flannel.yml: -------------------------------------------------------------------------------- 1 | name: Sync Flannel 2 | 3 | on: 4 | push: 5 | branches: [ disabled ] 6 | pull_request: 7 | branches: [ disabled ] 8 | 9 | jobs: 10 | build: 11 | name: Build imgsync 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Set up Go 1.14 15 | uses: actions/setup-go@v2 16 | with: 17 | go-version: 1.14 18 | id: go 19 | - name: Check out code 20 | uses: actions/checkout@v2 21 | - name: Cache go mod 22 | uses: actions/cache@v1 23 | with: 24 | path: ~/go/pkg/mod 25 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 26 | restore-keys: | 27 | ${{ runner.os }}-go- 28 | - name: Build binary file 29 | run: make bin 30 | - name: Upload artifact 31 | uses: actions/upload-artifact@v1 32 | with: 33 | name: imgsync 34 | path: imgsync 35 | sync_flannel: 36 | name: Sync Flannel 37 | needs: build 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Download artifact 41 | uses: actions/download-artifact@v1 42 | with: 43 | name: imgsync 44 | path: ./imgsync 45 | - name: Cache manifests 46 | uses: actions/cache@v1 47 | with: 48 | path: manifests 49 | key: flannel 50 | - name: Sync flannel images 51 | env: 52 | TZ: Asia/Shanghai 53 | DOCKER_USER: ${{ secrets.DOCKER_USER }} 54 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 55 | run: | 56 | chmod +x ./imgsync/imgsync 57 | ./imgsync/imgsync flannel --user ${DOCKER_USER} --password ${DOCKER_PASSWORD} -------------------------------------------------------------------------------- /.github/workflows/google-samples.yml: -------------------------------------------------------------------------------- 1 | name: Sync Google-samples 2 | 3 | on: 4 | push: 5 | branches: [ disabled ] 6 | pull_request: 7 | branches: [ disabled ] 8 | 9 | jobs: 10 | build: 11 | name: Build imgsync 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Set up Go 1.14 15 | uses: actions/setup-go@v2 16 | with: 17 | go-version: 1.14 18 | id: go 19 | - name: Check out code 20 | uses: actions/checkout@v2 21 | - name: Cache go mod 22 | uses: actions/cache@v1 23 | with: 24 | path: ~/go/pkg/mod 25 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 26 | restore-keys: | 27 | ${{ runner.os }}-go- 28 | - name: Build binary file 29 | run: make bin 30 | - name: Upload artifact 31 | uses: actions/upload-artifact@v1 32 | with: 33 | name: imgsync 34 | path: imgsync 35 | sync_distroless: 36 | name: Sync google-samples 37 | needs: build 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Download artifact 41 | uses: actions/download-artifact@v1 42 | with: 43 | name: imgsync 44 | path: ./imgsync 45 | - name: Cache manifests 46 | uses: actions/cache@v1 47 | with: 48 | path: manifests 49 | key: google-samples 50 | - name: Sync google-samples images 51 | env: 52 | TZ: Asia/Shanghai 53 | DOCKER_USER: ${{ secrets.DOCKER_USER }} 54 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 55 | run: | 56 | chmod +x ./imgsync/imgsync 57 | ./imgsync/imgsync gcr --namespace google-samples --user ${DOCKER_USER} --password ${DOCKER_PASSWORD} -------------------------------------------------------------------------------- /.github/workflows/helm.yml: -------------------------------------------------------------------------------- 1 | name: Sync Helm 2 | 3 | on: 4 | push: 5 | branches: [ disabled ] 6 | pull_request: 7 | branches: [ disabled ] 8 | 9 | jobs: 10 | build: 11 | name: Build imgsync 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Set up Go 1.14 15 | uses: actions/setup-go@v2 16 | with: 17 | go-version: 1.14 18 | id: go 19 | - name: Check out code 20 | uses: actions/checkout@v2 21 | - name: Cache go mod 22 | uses: actions/cache@v1 23 | with: 24 | path: ~/go/pkg/mod 25 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 26 | restore-keys: | 27 | ${{ runner.os }}-go- 28 | - name: Build binary file 29 | run: make bin 30 | - name: Upload artifact 31 | uses: actions/upload-artifact@v1 32 | with: 33 | name: imgsync 34 | path: imgsync 35 | sync_helm: 36 | name: Sync kubernetes-helm 37 | needs: build 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Download artifact 41 | uses: actions/download-artifact@v1 42 | with: 43 | name: imgsync 44 | path: ./imgsync 45 | - name: Cache manifests 46 | uses: actions/cache@v1 47 | with: 48 | path: manifests 49 | key: kubernetes-helm 50 | - name: Sync kubernetes-helm images 51 | env: 52 | TZ: Asia/Shanghai 53 | DOCKER_USER: ${{ secrets.DOCKER_USER }} 54 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 55 | run: | 56 | chmod +x ./imgsync/imgsync 57 | ./imgsync/imgsync gcr --namespace kubernetes-helm --user ${DOCKER_USER} --password ${DOCKER_PASSWORD} -------------------------------------------------------------------------------- /.github/workflows/istio.yml: -------------------------------------------------------------------------------- 1 | name: Sync Istio 2 | 3 | on: 4 | push: 5 | branches: [ disabled ] 6 | pull_request: 7 | branches: [ disabled ] 8 | 9 | jobs: 10 | build: 11 | name: Build imgsync 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Set up Go 1.14 15 | uses: actions/setup-go@v2 16 | with: 17 | go-version: 1.14 18 | id: go 19 | - name: Check out code 20 | uses: actions/checkout@v2 21 | - name: Cache go mod 22 | uses: actions/cache@v1 23 | with: 24 | path: ~/go/pkg/mod 25 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 26 | restore-keys: | 27 | ${{ runner.os }}-go- 28 | - name: Build binary file 29 | run: make bin 30 | - name: Upload artifact 31 | uses: actions/upload-artifact@v1 32 | with: 33 | name: imgsync 34 | path: imgsync 35 | sync_istio: 36 | name: Sync istio-release 37 | needs: build 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Download artifact 41 | uses: actions/download-artifact@v1 42 | with: 43 | name: imgsync 44 | path: ./imgsync 45 | - name: Cache manifests 46 | uses: actions/cache@v1 47 | with: 48 | path: manifests 49 | key: istio-release 50 | - name: Sync istio-release images 51 | env: 52 | TZ: Asia/Shanghai 53 | DOCKER_USER: ${{ secrets.DOCKER_USER }} 54 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 55 | run: | 56 | chmod +x ./imgsync/imgsync 57 | ./imgsync/imgsync gcr --namespace istio-release --user ${DOCKER_USER} --password ${DOCKER_PASSWORD} -------------------------------------------------------------------------------- /.github/workflows/kubeadm.yml: -------------------------------------------------------------------------------- 1 | name: Sync Kubeadm 2 | 3 | on: 4 | push: 5 | branches: [ disabled ] 6 | pull_request: 7 | branches: [ disabled ] 8 | 9 | jobs: 10 | build: 11 | name: Build imgsync 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Set up Go 1.14 15 | uses: actions/setup-go@v2 16 | with: 17 | go-version: 1.14 18 | id: go 19 | - name: Check out code 20 | uses: actions/checkout@v2 21 | - name: Cache go mod 22 | uses: actions/cache@v1 23 | with: 24 | path: ~/go/pkg/mod 25 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 26 | restore-keys: | 27 | ${{ runner.os }}-go- 28 | - name: Build binary file 29 | run: make bin 30 | - name: Upload artifact 31 | uses: actions/upload-artifact@v1 32 | with: 33 | name: imgsync 34 | path: imgsync 35 | sync_k8s: 36 | name: Sync k8s.gcr.io 37 | needs: build 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Download artifact 41 | uses: actions/download-artifact@v1 42 | with: 43 | name: imgsync 44 | path: ./imgsync 45 | - name: Cache manifests 46 | uses: actions/cache@v1 47 | with: 48 | path: manifests 49 | key: kubeadm 50 | - name: Sync k8s.gcr.io images 51 | env: 52 | TZ: Asia/Shanghai 53 | DOCKER_USER: ${{ secrets.DOCKER_USER }} 54 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 55 | run: | 56 | chmod +x ./imgsync/imgsync 57 | ./imgsync/imgsync gcr --kubeadm --user ${DOCKER_USER} --password ${DOCKER_PASSWORD} -------------------------------------------------------------------------------- /.github/workflows/linkerd.yml: -------------------------------------------------------------------------------- 1 | name: Sync Linkerd 2 | 3 | on: 4 | push: 5 | branches: [ disabled ] 6 | pull_request: 7 | branches: [ disabled ] 8 | 9 | jobs: 10 | build: 11 | name: Build imgsync 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Set up Go 1.14 15 | uses: actions/setup-go@v2 16 | with: 17 | go-version: 1.14 18 | id: go 19 | - name: Check out code 20 | uses: actions/checkout@v2 21 | - name: Cache go mod 22 | uses: actions/cache@v1 23 | with: 24 | path: ~/go/pkg/mod 25 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 26 | restore-keys: | 27 | ${{ runner.os }}-go- 28 | - name: Build binary file 29 | run: make bin 30 | - name: Upload artifact 31 | uses: actions/upload-artifact@v1 32 | with: 33 | name: imgsync 34 | path: imgsync 35 | sync_linkerd: 36 | name: Sync linkerd-io 37 | needs: build 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Download artifact 41 | uses: actions/download-artifact@v1 42 | with: 43 | name: imgsync 44 | path: ./imgsync 45 | - name: Cache manifests 46 | uses: actions/cache@v1 47 | with: 48 | path: manifests 49 | key: linkerd-io 50 | - name: Sync linkerd-io images 51 | env: 52 | TZ: Asia/Shanghai 53 | DOCKER_USER: ${{ secrets.DOCKER_USER }} 54 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 55 | run: | 56 | chmod +x ./imgsync/imgsync 57 | ./imgsync/imgsync gcr --namespace linkerd-io --user ${DOCKER_USER} --password ${DOCKER_PASSWORD} -------------------------------------------------------------------------------- /.github/workflows/spinnaker.yml: -------------------------------------------------------------------------------- 1 | name: Sync Spinnaker 2 | 3 | on: 4 | push: 5 | branches: [ disabled ] 6 | pull_request: 7 | branches: [ disabled ] 8 | 9 | jobs: 10 | build: 11 | name: Build imgsync 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Set up Go 1.14 15 | uses: actions/setup-go@v2 16 | with: 17 | go-version: 1.14 18 | id: go 19 | - name: Check out code 20 | uses: actions/checkout@v2 21 | - name: Cache go mod 22 | uses: actions/cache@v1 23 | with: 24 | path: ~/go/pkg/mod 25 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 26 | restore-keys: | 27 | ${{ runner.os }}-go- 28 | - name: Build binary file 29 | run: make bin 30 | - name: Upload artifact 31 | uses: actions/upload-artifact@v1 32 | with: 33 | name: imgsync 34 | path: imgsync 35 | sync_spinnaker: 36 | name: Sync spinnaker-marketplace 37 | needs: build 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Download artifact 41 | uses: actions/download-artifact@v1 42 | with: 43 | name: imgsync 44 | path: ./imgsync 45 | - name: Cache manifests 46 | uses: actions/cache@v1 47 | with: 48 | path: manifests 49 | key: spinnaker-marketplace 50 | - name: Sync spinnaker-marketplace images 51 | env: 52 | TZ: Asia/Shanghai 53 | DOCKER_USER: ${{ secrets.DOCKER_USER }} 54 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 55 | run: | 56 | chmod +x ./imgsync/imgsync 57 | ./imgsync/imgsync gcr --namespace spinnaker-marketplace --user ${DOCKER_USER} --password ${DOCKER_PASSWORD} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | dist 15 | imgsync 16 | manifests -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | depguard: 3 | list-type: blacklist 4 | packages: 5 | packages-with-error-message: 6 | dupl: 7 | threshold: 100 8 | funlen: 9 | lines: 100 10 | statements: 50 11 | goconst: 12 | min-len: 2 13 | min-occurrences: 2 14 | gocritic: 15 | enabled-tags: 16 | - diagnostic 17 | - experimental 18 | - opinionated 19 | - performance 20 | - style 21 | disabled-checks: 22 | - dupImport # https://github.com/go-critic/go-critic/issues/845 23 | - ifElseChain 24 | - octalLiteral 25 | - whyNoLint 26 | - wrapperFunc 27 | gocyclo: 28 | min-complexity: 15 29 | goimports: 30 | local-prefixes: github.com/mritd/imgsync 31 | golint: 32 | min-confidence: 0 33 | gomnd: 34 | settings: 35 | mnd: 36 | # don't include the "operation" and "assign" 37 | checks: argument,case,condition,return 38 | govet: 39 | check-shadowing: true 40 | lll: 41 | line-length: 140 42 | # maligned: 43 | # suggest-new: true 44 | misspell: 45 | locale: zh-CN 46 | 47 | linters: 48 | # please, do not use `enable-all`: it's deprecated and will be removed soon. 49 | # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint 50 | disable-all: true 51 | enable: 52 | - bodyclose 53 | - deadcode 54 | - depguard 55 | - dogsled 56 | - dupl 57 | - errcheck 58 | - funlen 59 | - gochecknoinits 60 | - goconst 61 | - gocritic 62 | - gocyclo 63 | - gofmt 64 | - golint 65 | - gomnd 66 | - goprintffuncname 67 | - gosec 68 | - gosimple 69 | - govet 70 | - ineffassign 71 | - interfacer 72 | - lll 73 | - misspell 74 | - nakedret 75 | - rowserrcheck 76 | - scopelint 77 | - staticcheck 78 | - structcheck 79 | - stylecheck 80 | - typecheck 81 | - unconvert 82 | - unparam 83 | - unused 84 | - varcheck 85 | - whitespace 86 | 87 | # don't enable: 88 | # - gochecknoglobals 89 | # - gocognit 90 | # - godox 91 | # - maligned 92 | # - prealloc 93 | 94 | issues: 95 | # Excluding configuration per-path, per-linter, per-text and per-source 96 | exclude-rules: 97 | - path: _test\.go 98 | linters: 99 | - gomnd 100 | 101 | run: 102 | skip-dirs: 103 | - test/testdata_etc 104 | - internal/cache 105 | - internal/renameio 106 | - internal/robustio 107 | 108 | # golangci.com configuration 109 | # https://github.com/golangci/golangci/wiki/Configuration 110 | service: 111 | golangci-lint-version: 1.23.x # use the fixed version to not introduce new linters unexpectedly 112 | prepare: 113 | - echo "here I can run custom commands, but no preparation needed for this repo" -------------------------------------------------------------------------------- /.logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mritd/imgsync/b6f2df3a9ef4fa1f28c0ea698f71e104162e5b19/.logo.png -------------------------------------------------------------------------------- /.travis-after-success.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | export TZ=UTC-8 6 | cd ${GCR_REPO} 7 | if [ -n "$(git status --porcelain)" ]; then 8 | git add . 9 | git commit -m "Travis CI Auto Update(`date +'%Y-%m-%d %H:%M:%S'`)" 10 | git push https://mritd:${GITHUB_TOKEN}@github.com/mritd/gcr.git 11 | fi 12 | -------------------------------------------------------------------------------- /.travis-before.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | rm -rf ${GCR_REPO} 6 | git clone https://mritd:${GITHUB_TOKEN}@github.com/mritd/gcr.git ${GCR_REPO} 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | os: linux 4 | dist: bionic 5 | go: 1.14.x 6 | 7 | cache: 8 | directories: 9 | - ${HOME}/.cache/go-build 10 | - ${GOPATH}/pkg/mod 11 | - ${GOPATH}/bin 12 | 13 | env: 14 | global: 15 | - GCR_REPO: ${HOME}/gcr 16 | 17 | stages: 18 | - Build imgsync 19 | - Sync Kubeadm 20 | - Sync Flannel 21 | - Sync Helm 22 | - Sync Istio 23 | - Sync Distroless 24 | - Sync Samples 25 | - Sync Linkerd 26 | - Sync Spinnaker 27 | - Sync KNative 28 | 29 | before_script: 30 | - bash .travis-before.sh 31 | 32 | jobs: 33 | include: 34 | - name: Build imgsync 35 | stage: Build imgsync 36 | script: 37 | - make install 38 | - name: Sync Kubeadm 39 | stage: Sync Kubeadm 40 | script: 41 | - imgsync gcr --kubeadm --user ${DOCKER_USER} --password ${DOCKER_PASSWORD} --process-limit 30 --manifests ${GCR_REPO}/manifests --report --report-name kubeadm --telegram-group ${TG_GROUP} --telegram-token ${TG_TOKEN} 42 | - name: Sync Flannel 43 | stage: Sync Flannel 44 | script: 45 | - imgsync flannel --user ${DOCKER_USER} --password ${DOCKER_PASSWORD} --process-limit 30 --manifests ${GCR_REPO}/manifests --report --report-name flannel --telegram-group ${TG_GROUP} --telegram-token ${TG_TOKEN} 46 | - name: Sync Helm 47 | stage: Sync Helm 48 | script: 49 | - imgsync gcr --namespace kubernetes-helm --user ${DOCKER_USER} --password ${DOCKER_PASSWORD} --process-limit 30 --manifests ${GCR_REPO}/manifests --report --report-name kubernetes-helm --telegram-group ${TG_GROUP} --telegram-token ${TG_TOKEN} 50 | - name: Sync Istio 51 | stage: Sync Istio 52 | script: 53 | - imgsync gcr --namespace istio-release --user ${DOCKER_USER} --password ${DOCKER_PASSWORD} --process-limit 30 --manifests ${GCR_REPO}/manifests --report --report-name istio-release --telegram-group ${TG_GROUP} --telegram-token ${TG_TOKEN} 54 | - name: Sync Distroless 55 | stage: Sync Distroless 56 | script: 57 | - imgsync gcr --namespace distroless --user ${DOCKER_USER} --password ${DOCKER_PASSWORD} --process-limit 30 --manifests ${GCR_REPO}/manifests --report --report-name distroless --telegram-group ${TG_GROUP} --telegram-token ${TG_TOKEN} 58 | - name: Sync Samples 59 | stage: Sync Samples 60 | script: 61 | - imgsync gcr --namespace google-samples --user ${DOCKER_USER} --password ${DOCKER_PASSWORD} --process-limit 30 --manifests ${GCR_REPO}/manifests --report --report-name google-samples --telegram-group ${TG_GROUP} --telegram-token ${TG_TOKEN} 62 | - name: Sync Linkerd 63 | stage: Sync Linkerd 64 | script: 65 | - imgsync gcr --namespace linkerd-io --user ${DOCKER_USER} --password ${DOCKER_PASSWORD} --process-limit 30 --manifests ${GCR_REPO}/manifests --report --report-name linkerd-io --telegram-group ${TG_GROUP} --telegram-token ${TG_TOKEN} 66 | - name: Sync Spinnaker 67 | stage: Sync Spinnaker 68 | script: 69 | - imgsync gcr --namespace spinnaker-marketplace --user ${DOCKER_USER} --password ${DOCKER_PASSWORD} --process-limit 30 --manifests ${GCR_REPO}/manifests --report --report-name spinnaker-marketplace --telegram-group ${TG_GROUP} --telegram-token ${TG_TOKEN} 70 | - name: Sync KNative 71 | stage: Sync KNative 72 | script: 73 | - imgsync gcr --namespace knative-releases --user ${DOCKER_USER} --password ${DOCKER_PASSWORD} --process-limit 30 --manifests ${GCR_REPO}/manifests --report --report-name knative-releases --telegram-group ${TG_GROUP} --telegram-token ${TG_TOKEN} 74 | 75 | after_success: 76 | - bash .travis-after-success.sh 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 mritd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BUILD_VERSION := $(shell cat version) 2 | BUILD_TIME := $(shell date "+%F %T") 3 | COMMIT_SHA1 := $(shell git rev-parse HEAD) 4 | 5 | all: 6 | gox -osarch="darwin/amd64 linux/386 linux/amd64" \ 7 | -output="dist/{{.Dir}}_{{.OS}}_{{.Arch}}" \ 8 | -tags="containers_image_openpgp" \ 9 | -ldflags "-X 'github.com/mritd/imgsync/cmd.version=${BUILD_VERSION}' \ 10 | -X 'github.com/mritd/imgsync/cmd.buildTime=${BUILD_TIME}' \ 11 | -X 'github.com/mritd/imgsync/cmd.commit=${COMMIT_SHA1}'" 12 | 13 | pre-release: all 14 | ghr -u mritd -t ${GITHUB_TOKEN} -replace -prerelease --debug ${BUILD_VERSION} dist 15 | 16 | release: all 17 | ghr -u mritd -t ${GITHUB_TOKEN} -replace -recreate --debug ${BUILD_VERSION} dist 18 | 19 | clean: 20 | rm -rf dist 21 | 22 | install: 23 | go install -tags="containers_image_openpgp" \ 24 | -ldflags "-X 'github.com/mritd/imgsync/cmd.version=${BUILD_VERSION}' \ 25 | -X 'github.com/mritd/imgsync/cmd.buildTime=${BUILD_TIME}' \ 26 | -X 'github.com/mritd/imgsync/cmd.commit=${COMMIT_SHA1}'" 27 | 28 | bin: 29 | go build -tags="containers_image_openpgp" \ 30 | -ldflags "-X 'github.com/mritd/imgsync/cmd.version=${BUILD_VERSION}' \ 31 | -X 'github.com/mritd/imgsync/cmd.buildTime=${BUILD_TIME}' \ 32 | -X 'github.com/mritd/imgsync/cmd.commit=${COMMIT_SHA1}'" 33 | 34 | .PHONY: all release clean install bin 35 | 36 | .EXPORT_ALL_VARIABLES: 37 | 38 | GO111MODULE = on 39 | GOPROXY = https://goproxy.cn 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![imgsync](.logo.png) 2 | 3 | ## imgsync 4 | 5 | A docker image sync tool. 6 | 7 | |Registry|Address|Docker Hub|Status| 8 | |--------|-------|----------|------| 9 | |Flannel|[quay.io/coreos/flannel](https://quay.io/coreos/flannel)|`gcrxio/quay.io_coreos_flannel`|[![Build Status](https://travis-ci.org/mritd/imgsync.svg?branch=master)](https://travis-ci.org/mritd/imgsync)| 10 | |kubeadm|[k8s.gcr.io](https://k8s.gcr.io)|`gcrxio/k8s.gcr.io_*`|[![Build Status](https://travis-ci.org/mritd/imgsync.svg?branch=master)](https://travis-ci.org/mritd/imgsync)| 11 | |Helm|[gcr.io/kubernetes-helm](https://gcr.io/kubernetes-helm)|`gcrxio/gcr.io_kubernetes-helm_*`|[![Build Status](https://travis-ci.org/mritd/imgsync.svg?branch=master)](https://travis-ci.org/mritd/imgsync)| 12 | |Istio|[gcr.io/istio-release](https://gcr.io/istio-release)|`gcrxio/gcr.io_istio-release_*`|[![Build Status](https://travis-ci.org/mritd/imgsync.svg?branch=master)](https://travis-ci.org/mritd/imgsync)| 13 | |Linkerd|[gcr.io/linkerd-io](https://gcr.io/linkerd-io)|`gcrxio/gcr.io_linkerd-io_*`|[![Build Status](https://travis-ci.org/mritd/imgsync.svg?branch=master)](https://travis-ci.org/mritd/imgsync)| 14 | |Spinnaker|[gcr.io/spinnaker-marketplace](https://gcr.io/spinnaker-marketplace)|`gcrxio/gcr.io_spinnaker-marketplace_*`|[![Build Status](https://travis-ci.org/mritd/imgsync.svg?branch=master)](https://travis-ci.org/mritd/imgsync)| 15 | |Distroless|[gcr.io/distroless](https://gcr.io/distroless)|`gcrxio/gcr.io_distroless_*`|[![Build Status](https://travis-ci.org/mritd/imgsync.svg?branch=master)](https://travis-ci.org/mritd/imgsync)| 16 | |Samples|[gcr.io/google-samples](https://gcr.io/google-samples)|`gcrxio/gcr.io_google-samples_*`|[![Build Status](https://travis-ci.org/mritd/imgsync.svg?branch=master)](https://travis-ci.org/mritd/imgsync)| 17 | |KNative|[gcr.io/knative-releases](https://gcr.io/knative-releases)|`gcrxio/gcr.io_knative-releases_*`|[![Build Status](https://travis-ci.org/mritd/imgsync.svg?branch=master)](https://travis-ci.org/mritd/imgsync)| 18 | 19 | **如何快速拉取 kubeadm 镜像?** 20 | 21 | ```sh 22 | for img in `kubeadm config images list`; do 23 | docker pull "gcrxio/$(echo $img | tr '/' '_')" && docker tag "gcrxio/$(echo $img | tr '/' '_')" $img; 24 | done 25 | ``` 26 | 27 | ## 特性 28 | 29 | - **不依赖 Docker 运行** 30 | - **基于 Manifests 同步** 31 | - **支持 [Fat Manifests](https://medium.com/@arunrajeevan/handling-multi-platform-deployment-using-manifest-file-in-docker-317736a2a039) 镜像同步** 32 | - **Manifests 文件本地 Cache,按需同步** 33 | - **同步期间不占用本地磁盘空间(直接通过标准库转发镜像)** 34 | - **可控的并发同步(优雅关闭/可调节并发数量)** 35 | - **按批次同步,支持同步指定区间段镜像** 36 | - **支持多仓库同步(后续仓库增加请提交 issue)** 37 | - **支持生成同步报告,同步报告推送 [Telegram](https://t.me/imgsync)** 38 | 39 | ## 安装 40 | 41 | 工具采用 go 编写,安装可直接从 release 页下载对应平台二进制文件,并增加可执行权限运行既可 42 | 43 | ```bash 44 | wget https://github.com/mritd/imgsync/releases/download/v2.0.0/imgsync_linux_amd64 45 | chmod +x imgsync_linux_amd64 46 | ./imgsync_linux_amd64 --help 47 | ``` 48 | 49 | ## 编译 50 | 51 | 如果预编译不包含您的平台架构,可以自行编译本工具,本工具编译依赖如下 52 | 53 | - make 54 | - git 55 | - Go 1.14.2+ 56 | 57 | 编译时直接运行 `make bin` 命令既可,如需交叉编译请安装 [gox](https://github.com/mitchellh/gox) 工具并执行 `make` 命令既可 58 | 59 | ## 使用 60 | 61 | ```bash 62 | Docker image sync tool. 63 | 64 | Usage: 65 | imgsync [flags] 66 | imgsync [command] 67 | 68 | Available Commands: 69 | flannel Sync flannel images 70 | gcr Sync gcr images 71 | help Help about any command 72 | sync Sync single image 73 | 74 | Flags: 75 | --debug debug mode 76 | -h, --help help for imgsync 77 | -v, --version version for imgsync 78 | 79 | Use "imgsync [command] --help" for more information about a command.. 80 | ``` 81 | 82 | ### sync 83 | 84 | `sync` 子命令用于同步单个镜像,一般用于测试目的进行同步并查看相关日志 85 | 86 | ### gcr 87 | 88 | `gcr` 子命令用户同步 **gcr.io** 相关镜像,如果使用 `--kubeadm` 选项则同步 **k8s.gcr.io** 镜像 89 | 90 | ### flannel 91 | 92 | `flannel` 子命令用于同步 **quay.io** 的 flannel 镜像 93 | 94 | ## 推荐配置 95 | 96 | 由于工具会开启并发同步,且不经过 Docker,不进行本地缓存,所以本工具推荐的最低运行配置如下: 97 | 98 | - 4 核心 8G 内存 vps 99 | - 至少 100/M 对等的带宽接口 100 | - Ubuntu 18.04+ 系统环境 101 | - 磁盘至少保留 2G 可用空间(manifests 本地缓存需要用到一定空间) 102 | 103 | **本工具默认 20 并发进行同步处理,且每次同步针对每个镜像 tag 至少发出一次 manifest 请求; 104 | 这意味着当前(在本文档编写时)每次全部仓库同步至少发出 100083 个 manifests 请求以及其他试图 105 | 获取镜像名称列表、tag 列表的请求,在高并发下这需要服务器有足够的 CPU 和带宽能力;内存占用方面 106 | 目前还可以接受,主要内存消耗在启动时加载 manifests 配置文件并反序列化到内存 map,这期间大约 107 | 需要花费最高 10s 的时间(434M json 文件)。** 108 | 109 | ## 镜像名称 110 | 111 | 工具默认会转换原镜像名称,转换规则为将原镜像名称内的 `/` 全部替换为 `_`,例如(假设 Docker Hub 用户名为 `gcrxio`): 112 | 113 | **`gcr.io/istio-release/pilot:latest` ==> `gcrxio/gcr.io_istio-release_pilot:latest`** 114 | 115 | ## 国内 Docker Hub Mirror 116 | 117 | - Aliyun: `[系统分配前缀].mirror.aliyuncs.com` 118 | - Tencent: `https://mirror.ccs.tencentyun.com` 119 | - 163: `http://hub-mirror.c.163.com` 120 | - Azure: `dockerhub.azk8s.cn` 121 | 122 | ## 其他说明 123 | 124 | **工具目前仅支持同步到 Docker Hub,且以后没有同步到其他仓库打算。同步 Docker Hub 125 | 时默认会同步到 `--user` 指定的用户下;本工具默认已经将支持的仓库同步到 Docker Hub [gcrxio](https://hub.docker.com/u/gcrxio) 用户下; 126 | 其他更细节使用请自行通过 `--help` 查看以及参考本项目 [Travis CI](https://github.com/mritd/imgsync/tree/master/.travis.yml) 配置文件。 127 | 项目目录下的 Github Action 配置已经停用(性能不行),没删除是因为调了好久删了可惜,以后备用吧。** 128 | -------------------------------------------------------------------------------- /cmd/flannel.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/mritd/imgsync/core" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var flSyncOption core.SyncOption 9 | 10 | var flannelCmd = &cobra.Command{ 11 | Use: "flannel", 12 | Short: "Sync flannel images", 13 | Long: ` 14 | Sync flannel images.`, 15 | PreRun: prerun, 16 | Run: func(cmd *cobra.Command, args []string) { 17 | boot("flannel", &flSyncOption) 18 | }, 19 | } 20 | 21 | func init() { 22 | rootCmd.AddCommand(flannelCmd) 23 | flannelCmd.PersistentFlags().StringVar(&flSyncOption.User, "user", "", "docker hub user") 24 | flannelCmd.PersistentFlags().StringVar(&flSyncOption.Password, "password", "", "docker hub user password") 25 | flannelCmd.PersistentFlags().DurationVar(&flSyncOption.Timeout, "timeout", core.DefaultSyncTimeout, "sync single image timeout") 26 | flannelCmd.PersistentFlags().IntVar(&flSyncOption.Limit, "process-limit", core.DefaultLimit, "sync image limit") 27 | flannelCmd.PersistentFlags().BoolVar(&flSyncOption.OnlyDownloadManifests, "download-manifests", false, "only download manifests") 28 | flannelCmd.PersistentFlags().BoolVar(&flSyncOption.Report, "report", false, "report sync detail") 29 | flannelCmd.PersistentFlags().IntVar(&flSyncOption.ReportLevel, "report-level", 1, "report sync detail level") 30 | flannelCmd.PersistentFlags().StringVar(&gcrSyncOption.ReportName, "report-name", "flannel", "report name") 31 | flannelCmd.PersistentFlags().StringVar(&gcrSyncOption.TelegramApi, "telegram-api", "https://api.telegram.org", "telegram api address") 32 | flannelCmd.PersistentFlags().StringVar(&gcrSyncOption.TelegramToken, "telegram-token", "", "telegram bot token") 33 | flannelCmd.PersistentFlags().Int64Var(&gcrSyncOption.TelegramGroup, "telegram-group", 0, "telegram group id") 34 | flannelCmd.PersistentFlags().StringVar(&core.ManifestDir, "manifests", "manifests", "manifests storage dir") 35 | } 36 | -------------------------------------------------------------------------------- /cmd/gcr.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/mritd/imgsync/core" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var gcrSyncOption core.SyncOption 9 | 10 | var gcrCmd = &cobra.Command{ 11 | Use: "gcr", 12 | Short: "Sync gcr images", 13 | Long: ` 14 | Sync gcr images.`, 15 | PreRun: prerun, 16 | Run: func(cmd *cobra.Command, args []string) { 17 | boot("gcr", &gcrSyncOption) 18 | }, 19 | } 20 | 21 | func init() { 22 | rootCmd.AddCommand(gcrCmd) 23 | gcrCmd.PersistentFlags().StringVar(&gcrSyncOption.User, "user", "", "docker hub user") 24 | gcrCmd.PersistentFlags().StringVar(&gcrSyncOption.Password, "password", "", "docker hub user password") 25 | gcrCmd.PersistentFlags().StringVar(&gcrSyncOption.NameSpace, "namespace", "google-containers", "google container registry namespace") 26 | gcrCmd.PersistentFlags().IntVar(&gcrSyncOption.QueryLimit, "query-limit", core.DefaultLimit, "http query limit") 27 | gcrCmd.PersistentFlags().IntVar(&gcrSyncOption.Limit, "process-limit", core.DefaultLimit, "sync image limit") 28 | gcrCmd.PersistentFlags().DurationVar(&gcrSyncOption.Timeout, "timeout", core.DefaultSyncTimeout, "sync single image timeout") 29 | gcrCmd.PersistentFlags().BoolVar(&gcrSyncOption.Kubeadm, "kubeadm", false, "sync kubeadm images(ignore namespace, use k8s.gcr.io)") 30 | gcrCmd.PersistentFlags().IntVar(&gcrSyncOption.BatchSize, "batch-size", 0, "batch size") 31 | gcrCmd.PersistentFlags().IntVar(&gcrSyncOption.BatchNumber, "batch-number", 0, "batch number") 32 | gcrCmd.PersistentFlags().BoolVar(&gcrSyncOption.OnlyDownloadManifests, "download-manifests", false, "only download manifests") 33 | gcrCmd.PersistentFlags().BoolVar(&gcrSyncOption.Report, "report", false, "report sync detail") 34 | gcrCmd.PersistentFlags().IntVar(&gcrSyncOption.ReportLevel, "report-level", 1, "report sync detail level") 35 | gcrCmd.PersistentFlags().StringVar(&gcrSyncOption.ReportName, "report-name", "kubeadm", "report name") 36 | gcrCmd.PersistentFlags().StringVar(&gcrSyncOption.TelegramApi, "telegram-api", "https://api.telegram.org", "telegram api address") 37 | gcrCmd.PersistentFlags().StringVar(&gcrSyncOption.TelegramToken, "telegram-token", "", "telegram bot token") 38 | gcrCmd.PersistentFlags().Int64Var(&gcrSyncOption.TelegramGroup, "telegram-group", 0, "telegram group id") 39 | gcrCmd.PersistentFlags().StringVar(&core.ManifestDir, "manifests", "manifests", "manifests storage dir") 40 | } 41 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | "runtime" 9 | "sync" 10 | "syscall" 11 | 12 | "github.com/mritd/imgsync/core" 13 | 14 | "github.com/sirupsen/logrus" 15 | 16 | "github.com/spf13/cobra" 17 | ) 18 | 19 | var version, buildTime, commit string 20 | 21 | var debug bool 22 | 23 | var rootCmd = &cobra.Command{ 24 | Use: "imgsync", 25 | Short: "Docker image sync tool", 26 | Version: version, 27 | Long: ` 28 | Docker image sync tool.`, 29 | Run: func(cmd *cobra.Command, args []string) { 30 | _ = cmd.Help() 31 | }, 32 | } 33 | 34 | func Execute() { 35 | if err := rootCmd.Execute(); err != nil { 36 | logrus.Fatal(err) 37 | } 38 | } 39 | 40 | func init() { 41 | cobra.OnInitialize(initLog) 42 | rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "debug mode") 43 | rootCmd.SetVersionTemplate(versionTpl()) 44 | } 45 | 46 | func initLog() { 47 | logrus.SetFormatter(&logrus.TextFormatter{ 48 | FullTimestamp: true, 49 | TimestampFormat: "2006-01-02 15:04:05", 50 | }) 51 | 52 | if debug { 53 | logrus.SetLevel(logrus.DebugLevel) 54 | } 55 | } 56 | 57 | func versionTpl() string { 58 | var tpl = `%s 59 | Name: imgsync 60 | Version: %s 61 | Arch: %s 62 | BuildTime: %s 63 | CommitID: %s 64 | ` 65 | return fmt.Sprintf(tpl, core.Banner, version, runtime.GOOS+"/"+runtime.GOARCH, buildTime, commit) 66 | } 67 | 68 | func prerun(_ *cobra.Command, _ []string) { 69 | if err := core.LoadManifests(); err != nil { 70 | logrus.Fatalf("failed to load manifests: %s", err) 71 | } 72 | } 73 | 74 | func boot(name string, opt *core.SyncOption) { 75 | sigs := make(chan os.Signal) 76 | ctx, cancel := context.WithCancel(context.Background()) 77 | var cancelOnce sync.Once 78 | defer cancel() 79 | go func() { 80 | for range sigs { 81 | cancelOnce.Do(func() { 82 | logrus.Info("Receiving a termination signal, gracefully shutdown!") 83 | cancel() 84 | }) 85 | logrus.Info("The goroutines pool has stopped, please wait for the remaining tasks to complete.") 86 | } 87 | }() 88 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 89 | core.NewSynchronizer(name).Sync(ctx, opt) 90 | } 91 | -------------------------------------------------------------------------------- /cmd/sync.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/sirupsen/logrus" 8 | 9 | "github.com/mritd/imgsync/core" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var syncOption core.SyncOption 14 | 15 | var syncCmd = &cobra.Command{ 16 | Use: "sync", 17 | Short: "Sync single image", 18 | Long: ` 19 | Sync single image.`, 20 | PreRun: prerun, 21 | Run: func(cmd *cobra.Command, args []string) { 22 | if len(args) != 1 { 23 | _ = cmd.Help() 24 | return 25 | } 26 | var repo, user, name, tag string 27 | ss := strings.Split(args[0], ":") 28 | if len(ss) == 1 { 29 | tag = "latest" 30 | } else { 31 | tag = ss[len(ss)-1] 32 | } 33 | ss = strings.Split(ss[0], "/") 34 | switch len(ss) { 35 | case 1: 36 | name = ss[0] 37 | case 2: 38 | repo = ss[0] 39 | name = ss[1] 40 | case 3: 41 | repo = ss[0] 42 | user = ss[1] 43 | name = ss[2] 44 | default: 45 | logrus.Fatalf("image name format error: %s", args[0]) 46 | } 47 | core.SyncImages(context.Background(), core.Images{&core.Image{ 48 | Repo: repo, 49 | User: user, 50 | Name: name, 51 | Tag: tag, 52 | }}, &syncOption) 53 | }, 54 | } 55 | 56 | func init() { 57 | rootCmd.AddCommand(syncCmd) 58 | syncCmd.PersistentFlags().StringVar(&syncOption.User, "user", "", "docker hub user") 59 | syncCmd.PersistentFlags().StringVar(&syncOption.Password, "password", "", "docker hub user password") 60 | syncCmd.PersistentFlags().StringVar(&syncOption.NameSpace, "namespace", "google-containers", "google container registry namespace") 61 | syncCmd.PersistentFlags().DurationVar(&syncOption.Timeout, "timeout", core.DefaultSyncTimeout, "sync single image timeout") 62 | syncCmd.PersistentFlags().BoolVar(&syncOption.OnlyDownloadManifests, "download-manifests", false, "only download manifests") 63 | syncCmd.PersistentFlags().StringVar(&core.ManifestDir, "manifests", "manifests", "manifests storage dir") 64 | } 65 | -------------------------------------------------------------------------------- /core/common.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "encoding/base64" 5 | "time" 6 | ) 7 | 8 | const ( 9 | DefaultLimit = 20 10 | DefaultSyncTimeout = 10 * time.Minute 11 | DefaultCtxTimeout = 5 * time.Minute 12 | DefaultHTTPTimeout = 30 * time.Second 13 | DefaultGoRequestRetry = 3 14 | DefaultGoRequestRetryTime = 5 * time.Second 15 | 16 | // DockerHubTags = "https://hub.docker.com/v2/repositories/%s/%s/tags/?page_size=100" 17 | // DockerHubImage = "https://hub.docker.com/v2/repositories/%s/?page_size=100" 18 | // GcrStandardManifestsTpl = "https://gcr.io/v2/%s/%s/manifests/%s" 19 | // GcrKubeadmManifestsTpl = "https://k8s.gcr.io/v2/%s/manifests/%s" 20 | // GcrStandardImageTagsTpl = "https://gcr.io/v2/%s/%s/tags/list" 21 | // GcrKubeadmImageTagsTpl = "https://k8s.gcr.io/v2/%s/tags/list" 22 | 23 | defaultSyncRetry = 3 24 | defaultSyncRetryTime = 10 * time.Second 25 | 26 | defaultDockerRepo = "docker.io" 27 | defaultK8sRepo = "k8s.gcr.io" 28 | defaultGcrRepo = "gcr.io" 29 | gcrStandardImagesTpl = "https://gcr.io/v2/%s/tags/list" 30 | flannelImageName = "quay.io/coreos/flannel" 31 | 32 | bannerBase64 = "ZSAgZWVlZWVlZSBlZWVlZSBlZWVlZSBlICAgIGUgZWVlZWUgZWVlZQo4ICA4ICA4ICA4IDggICA4IDggICAiIDggICAgOCA4ICAgOCA4ICA4CjhlIDhlIDggIDggOGUgICAgOGVlZWUgOGVlZWU4IDhlICA4IDhlCjg4IDg4IDggIDggODggIjggICAgODggICA4OCAgIDg4ICA4IDg4Cjg4IDg4IDggIDggODhlZTggOGVlODggICA4OCAgIDg4ICA4IDg4ZTgK" 33 | reportHeaderTpl = `%s 34 | ======================================== 35 | >> Sync Repo: %s 36 | >> Sync Total: %d 37 | >> Sync Failed: %d 38 | >> Sync Success: %d 39 | >> Manifests CacheHit: %d 40 | ` 41 | reportErrorTpl = `======================================== 42 | Sync failed images: 43 | {{range .}}{{if not .Success}}{{. | print}}: {{.Err | println}}{{end}}{{end}}` 44 | reportSuccessTpl = `======================================== 45 | Sync success images: 46 | {{range .}}{{if .Success}}{{. | print}}: {{if .CacheHit}}{{"hit cache" | println}}{{else}}{{"not hit cache" | println}}{{end}}{{end}}{{end}}` 47 | ) 48 | 49 | var ( 50 | ManifestDir = "manifests" 51 | Banner, _ = base64.StdEncoding.DecodeString(bannerBase64) 52 | ) 53 | 54 | func retry(count int, interval time.Duration, f func() error) error { 55 | var err error 56 | redo: 57 | count-- 58 | if err = f(); err != nil { 59 | if count > 0 { 60 | if interval > 0 { 61 | <-time.After(interval) 62 | } 63 | goto redo 64 | } 65 | } 66 | return err 67 | } 68 | -------------------------------------------------------------------------------- /core/manifests.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | 11 | jsoniter "github.com/json-iterator/go" 12 | imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" 13 | 14 | "github.com/containers/image/v5/docker" 15 | "github.com/containers/image/v5/types" 16 | 17 | "github.com/containers/image/v5/manifest" 18 | 19 | "github.com/sirupsen/logrus" 20 | ) 21 | 22 | var manifestsMap = make(map[string]interface{}, 5000) 23 | 24 | func LoadManifests() error { 25 | _, err := os.Stat(ManifestDir) 26 | if err != nil { 27 | if os.IsNotExist(err) { 28 | return os.MkdirAll(ManifestDir, 0755) 29 | } 30 | return err 31 | } 32 | 33 | logrus.Infof("loading manifests path [%s]...", ManifestDir) 34 | err = filepath.Walk(ManifestDir, func(path string, info os.FileInfo, ferr error) error { 35 | if ferr != nil { 36 | return ferr 37 | } 38 | if info.IsDir() { 39 | return nil 40 | } 41 | logrus.Debugf("loading manifest file: %s", path) 42 | ss := strings.Split(strings.TrimPrefix(path, ManifestDir), string(filepath.Separator)) 43 | prefix := strings.Join(ss[:len(ss)-1], "/") 44 | tag := strings.TrimSuffix(ss[len(ss)-1], ".json") 45 | cacheKey := strings.TrimPrefix(fmt.Sprintf("%s:%s", prefix, tag), "/") 46 | logrus.Debugf("manifest cache key: %s", cacheKey) 47 | mbs, rerr := ioutil.ReadFile(path) 48 | if rerr != nil { 49 | return rerr 50 | } 51 | 52 | mType := manifest.GuessMIMEType(mbs) 53 | // ignore blank json file 54 | if mType == "" { 55 | return nil 56 | } 57 | switch mType { 58 | case manifest.DockerV2ListMediaType: 59 | var m2List manifest.Schema2List 60 | if jerr := jsoniter.Unmarshal(mbs, &m2List); jerr == nil { 61 | manifestsMap[cacheKey] = &m2List 62 | } else { 63 | logrus.Debugf("failed to parse json [%s]: %s", path, err) 64 | } 65 | return nil 66 | case imgspecv1.MediaTypeImageIndex: 67 | var o1List manifest.OCI1Index 68 | if jerr := jsoniter.Unmarshal(mbs, &o1List); jerr == nil { 69 | manifestsMap[cacheKey] = &o1List 70 | } else { 71 | logrus.Debugf("failed to parse json [%s]: %s", path, err) 72 | } 73 | return nil 74 | default: 75 | if m, jerr := manifest.FromBlob(mbs, mType); jerr == nil { 76 | manifestsMap[cacheKey] = m 77 | } else { 78 | logrus.Debugf("failed to parse json [%s]: %s", path, err) 79 | } 80 | return nil 81 | } 82 | }) 83 | logrus.Infof("loaded manifests count: %d", len(manifestsMap)) 84 | return err 85 | } 86 | 87 | func getImageManifest(imageName string) (manifest.Manifest, manifest.List, error) { 88 | srcRef, err := docker.ParseReference("//" + imageName) 89 | if err != nil { 90 | return nil, nil, err 91 | } 92 | 93 | sourceCtx := &types.SystemContext{DockerAuthConfig: &types.DockerAuthConfig{}} 94 | imageSrcCtx, imageSrcCancel := context.WithTimeout(context.Background(), DefaultCtxTimeout) 95 | defer imageSrcCancel() 96 | src, err := srcRef.NewImageSource(imageSrcCtx, sourceCtx) 97 | if err != nil { 98 | return nil, nil, err 99 | } 100 | 101 | getManifestCtx, getManifestCancel := context.WithTimeout(context.Background(), DefaultCtxTimeout) 102 | defer getManifestCancel() 103 | mbs, _, err := src.GetManifest(getManifestCtx, nil) 104 | if err != nil { 105 | return nil, nil, err 106 | } 107 | 108 | mType := manifest.GuessMIMEType(mbs) 109 | if mType == "" { 110 | return nil, nil, fmt.Errorf("faile to parse image [%s] manifest type", imageName) 111 | } 112 | switch mType { 113 | case manifest.DockerV2ListMediaType: 114 | var m2List manifest.Schema2List 115 | err = jsoniter.Unmarshal(mbs, &m2List) 116 | if err != nil { 117 | return nil, nil, err 118 | } 119 | return nil, &m2List, nil 120 | case imgspecv1.MediaTypeImageIndex: 121 | var o1List manifest.OCI1Index 122 | err = jsoniter.Unmarshal(mbs, &o1List) 123 | if err != nil { 124 | return nil, nil, err 125 | } 126 | return nil, &o1List, nil 127 | default: 128 | m, err := manifest.FromBlob(mbs, mType) 129 | if err != nil { 130 | return nil, nil, err 131 | } 132 | return m, nil, nil 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /core/notification.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "time" 5 | 6 | tb "gopkg.in/tucnak/telebot.v2" 7 | ) 8 | 9 | func notification(msg, url, token string, group int64) error { 10 | bot, err := tb.NewBot(tb.Settings{ 11 | URL: url, 12 | Token: token, 13 | Poller: &tb.LongPoller{Timeout: 5 * time.Second}, 14 | }) 15 | if err != nil { 16 | return err 17 | } 18 | 19 | _, err = bot.Send(&tb.Chat{ID: group}, "```\n"+msg+"\n```", &tb.SendOptions{ParseMode: tb.ModeMarkdownV2}) 20 | return err 21 | } 22 | -------------------------------------------------------------------------------- /core/synchronizer.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "reflect" 11 | "sort" 12 | "sync" 13 | "text/template" 14 | "time" 15 | 16 | "github.com/panjf2000/ants/v2" 17 | 18 | "github.com/containers/image/v5/manifest" 19 | 20 | jsoniter "github.com/json-iterator/go" 21 | 22 | "github.com/containers/image/v5/copy" 23 | "github.com/containers/image/v5/docker" 24 | "github.com/containers/image/v5/signature" 25 | "github.com/containers/image/v5/types" 26 | 27 | "github.com/sirupsen/logrus" 28 | ) 29 | 30 | type Synchronizer interface { 31 | Images(ctx context.Context) Images 32 | Sync(ctx context.Context, opt *SyncOption) 33 | } 34 | 35 | type SyncOption struct { 36 | User string // Docker Hub User 37 | Password string // Docker Hub User Password 38 | Timeout time.Duration // Sync single image timeout 39 | Limit int // Images sync process limit 40 | BatchSize int // Batch size for batch synchronization 41 | BatchNumber int // Sync specified batch 42 | OnlyDownloadManifests bool // Only download Manifests file 43 | Report bool // Report sync result 44 | ReportName string // Report name 45 | ReportLevel int // Report level 46 | TelegramApi string // Telegram api address 47 | TelegramToken string // Telegram bot token 48 | TelegramGroup int64 // Telegram group id 49 | 50 | QueryLimit int // Query Gcr images limit 51 | NameSpace string // Gcr image namespace 52 | Kubeadm bool // Sync kubeadm images (change gcr.io to k8s.gcr.io, and remove namespace) 53 | } 54 | 55 | type TagsOption struct { 56 | Timeout time.Duration 57 | } 58 | 59 | func NewSynchronizer(name string) Synchronizer { 60 | switch name { 61 | case "gcr": 62 | return &gcr 63 | case "flannel": 64 | return &fl 65 | default: 66 | logrus.Fatalf("failed to create synchronizer %s: unknown synchronizer", name) 67 | // just for compiling 68 | return nil 69 | } 70 | } 71 | 72 | func SyncImages(ctx context.Context, images Images, opt *SyncOption) Images { 73 | imgs := batchProcess(images, opt) 74 | logrus.Infof("starting sync images, image total: %d", len(imgs)) 75 | 76 | processWg := new(sync.WaitGroup) 77 | processWg.Add(len(imgs)) 78 | 79 | if opt.Limit == 0 { 80 | opt.Limit = DefaultLimit 81 | } 82 | 83 | pool, err := ants.NewPool(opt.Limit, ants.WithPreAlloc(true), ants.WithPanicHandler(func(i interface{}) { 84 | logrus.Error(i) 85 | })) 86 | if err != nil { 87 | logrus.Fatalf("failed to create goroutines pool: %s", err) 88 | } 89 | sort.Sort(imgs) 90 | for i := 0; i < len(imgs); i++ { 91 | k := i 92 | err = pool.Submit(func() { 93 | defer processWg.Done() 94 | 95 | select { 96 | case <-ctx.Done(): 97 | default: 98 | logrus.Debugf("process image: %s", imgs[k].String()) 99 | m, l, needSync := checkSync(imgs[k]) 100 | if !needSync { 101 | return 102 | } 103 | var bs []byte 104 | if m != nil { 105 | bs, err = jsoniter.MarshalIndent(m, "", " ") 106 | } else { 107 | bs, err = jsoniter.MarshalIndent(l, "", " ") 108 | } 109 | if err != nil { 110 | logrus.Errorf("failed to storage image [%s] manifests: %s", imgs[k].String(), err) 111 | } 112 | logrus.Debug(string(bs)) 113 | 114 | rerr := retry(defaultSyncRetry, defaultSyncRetryTime, func() error { 115 | return sync2DockerHub(imgs[k], opt) 116 | }) 117 | if rerr != nil { 118 | imgs[k].Err = rerr 119 | logrus.Errorf("failed to process image %s, error: %s", imgs[k].String(), rerr) 120 | return 121 | } 122 | imgs[k].Success = true 123 | 124 | storageDir := filepath.Join(ManifestDir, imgs[k].Repo, imgs[k].User, imgs[k].Name) 125 | // ignore other error 126 | if _, err = os.Stat(storageDir); err != nil { 127 | if err = os.MkdirAll(storageDir, 0755); err != nil { 128 | logrus.Errorf("failed to storage image [%s] manifests: %s", imgs[k].String(), err) 129 | } 130 | } 131 | 132 | if ferr := ioutil.WriteFile(filepath.Join(storageDir, imgs[k].Tag+".json"), bs, 0644); ferr != nil { 133 | logrus.Errorf("failed to storage image [%s] manifests: %s", imgs[k].String(), ferr) 134 | } 135 | } 136 | }) 137 | if err != nil { 138 | logrus.Fatalf("failed to submit task: %s", err) 139 | } 140 | } 141 | processWg.Wait() 142 | pool.Release() 143 | return imgs 144 | } 145 | 146 | func sync2DockerHub(image *Image, opt *SyncOption) error { 147 | if opt.OnlyDownloadManifests { 148 | return nil 149 | } 150 | destImage := Image{ 151 | Repo: defaultDockerRepo, 152 | User: opt.User, 153 | Name: image.MergeName(), 154 | Tag: image.Tag, 155 | } 156 | 157 | logrus.Infof("syncing %s => %s", image.String(), destImage.String()) 158 | 159 | ctx, cancel := context.WithTimeout(context.Background(), opt.Timeout) 160 | defer cancel() 161 | 162 | policyContext, err := signature.NewPolicyContext( 163 | &signature.Policy{ 164 | Default: []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()}, 165 | }, 166 | ) 167 | if err != nil { 168 | return err 169 | } 170 | defer func() { _ = policyContext.Destroy() }() 171 | 172 | srcRef, err := docker.ParseReference("//" + image.String()) 173 | if err != nil { 174 | return err 175 | } 176 | destRef, err := docker.ParseReference("//" + destImage.String()) 177 | if err != nil { 178 | return err 179 | } 180 | 181 | sourceCtx := &types.SystemContext{DockerAuthConfig: &types.DockerAuthConfig{}} 182 | destinationCtx := &types.SystemContext{DockerAuthConfig: &types.DockerAuthConfig{ 183 | Username: opt.User, 184 | Password: opt.Password, 185 | }} 186 | 187 | logrus.Debugf("copy %s to docker hub...", image.String()) 188 | _, err = copy.Image(ctx, policyContext, destRef, srcRef, ©.Options{ 189 | SourceCtx: sourceCtx, 190 | DestinationCtx: destinationCtx, 191 | ImageListSelection: copy.CopyAllImages, 192 | }) 193 | logrus.Debugf("%s copy done.", image.String()) 194 | return err 195 | } 196 | 197 | func getImageTags(imageName string, opt TagsOption) ([]string, error) { 198 | srcRef, err := docker.ParseReference("//" + imageName) 199 | if err != nil { 200 | return nil, err 201 | } 202 | sourceCtx := &types.SystemContext{DockerAuthConfig: &types.DockerAuthConfig{}} 203 | tagsCtx, tagsCancel := context.WithTimeout(context.Background(), opt.Timeout) 204 | defer tagsCancel() 205 | return docker.GetRepositoryTags(tagsCtx, sourceCtx, srcRef) 206 | } 207 | 208 | func checkSync(image *Image) (manifest.Manifest, manifest.List, bool) { 209 | var m manifest.Manifest 210 | var l manifest.List 211 | var merr error 212 | 213 | err := retry(DefaultGoRequestRetry, DefaultGoRequestRetryTime, func() error { 214 | m, l, merr = getImageManifest(image.String()) 215 | if merr != nil { 216 | return merr 217 | } 218 | return nil 219 | }) 220 | 221 | if err != nil { 222 | image.Err = err 223 | logrus.Errorf("failed to get image [%s] manifest, error: %s", image.String(), err) 224 | return nil, nil, false 225 | } 226 | val, ok := manifestsMap[image.String()] 227 | if (ok && m != nil && reflect.DeepEqual(m, val)) || (ok && l != nil && reflect.DeepEqual(l, val)) { 228 | image.Success = true 229 | image.CacheHit = true 230 | logrus.Debugf("image [%s] not changed, skip sync...", image.String()) 231 | return nil, nil, false 232 | } 233 | return m, l, true 234 | } 235 | 236 | func batchProcess(images Images, opt *SyncOption) Images { 237 | if opt.BatchSize > 0 && opt.BatchNumber > 0 && len(images) > opt.BatchSize { 238 | n := len(images) / opt.BatchSize 239 | var start, end int 240 | if opt.BatchNumber >= n { 241 | start = opt.BatchSize * (n - 1) 242 | return images[start:] 243 | } 244 | start = opt.BatchSize * (opt.BatchNumber - 1) 245 | end = opt.BatchSize * opt.BatchNumber 246 | return images[start:end] 247 | } 248 | return images 249 | } 250 | 251 | func report(images Images, opt *SyncOption) { 252 | if !opt.Report { 253 | return 254 | } 255 | var successCount, failedCount, cacheHitCount int 256 | var report string 257 | 258 | for _, img := range images { 259 | if img.Success { 260 | successCount++ 261 | if img.CacheHit { 262 | cacheHitCount++ 263 | } 264 | } else { 265 | failedCount++ 266 | } 267 | } 268 | report = fmt.Sprintf(reportHeaderTpl, Banner, opt.ReportName, len(images), failedCount, successCount, cacheHitCount) 269 | 270 | if opt.ReportLevel > 1 { 271 | var buf bytes.Buffer 272 | reportError, _ := template.New("").Parse(reportErrorTpl) 273 | err := reportError.Execute(&buf, images) 274 | if err != nil { 275 | logrus.Errorf("failed to create report error: %s", err) 276 | } 277 | report += buf.String() 278 | } 279 | 280 | if opt.ReportLevel > 2 { 281 | var buf bytes.Buffer 282 | reportSuccess, _ := template.New("").Parse(reportSuccessTpl) 283 | err := reportSuccess.Execute(&buf, images) 284 | if err != nil { 285 | logrus.Errorf("failed to create report error: %s", err) 286 | } 287 | report += buf.String() 288 | } 289 | fmt.Println(report) 290 | err := notification(report, opt.TelegramApi, opt.TelegramToken, opt.TelegramGroup) 291 | if err != nil { 292 | logrus.Errorf("failed send report message to telegram: %s", err) 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /core/synchronizer_flannel.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/sirupsen/logrus" 8 | ) 9 | 10 | var fl Flannel 11 | 12 | type Flannel struct { 13 | } 14 | 15 | func (fl *Flannel) Images(ctx context.Context) Images { 16 | logrus.Infof("get flannel image tags") 17 | var images Images 18 | select { 19 | case <-ctx.Done(): 20 | default: 21 | tags, err := getImageTags(flannelImageName, TagsOption{Timeout: DefaultCtxTimeout}) 22 | if err != nil { 23 | logrus.Errorf("failed to get [%s] image tags, error: %s", flannelImageName, err) 24 | return nil 25 | } 26 | 27 | ss := strings.Split(flannelImageName, "/") 28 | for _, tag := range tags { 29 | images = append(images, &Image{ 30 | Repo: ss[0], 31 | User: ss[1], 32 | Name: ss[2], 33 | Tag: tag, 34 | }) 35 | } 36 | } 37 | 38 | return images 39 | } 40 | 41 | func (fl *Flannel) Sync(ctx context.Context, opt *SyncOption) { 42 | flImages := fl.setDefault(opt).Images(ctx) 43 | logrus.Infof("sync images count: %d", len(flImages)) 44 | imgs := SyncImages(ctx, flImages, opt) 45 | report(imgs, opt) 46 | } 47 | 48 | func (fl *Flannel) setDefault(_ *SyncOption) *Flannel { 49 | return fl 50 | } 51 | -------------------------------------------------------------------------------- /core/synchronizer_gcr.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | "time" 8 | 9 | jsoniter "github.com/json-iterator/go" 10 | "github.com/panjf2000/ants/v2" 11 | "github.com/parnurzeal/gorequest" 12 | 13 | "github.com/sirupsen/logrus" 14 | ) 15 | 16 | var gcr Gcr 17 | 18 | type Gcr struct { 19 | kubeadm bool 20 | queryLimit int 21 | namespace string 22 | } 23 | 24 | func (gcr *Gcr) Images(ctx context.Context) Images { 25 | logrus.Info("get gcr public images...") 26 | if gcr.kubeadm { 27 | gcr.namespace = "google-containers" 28 | } 29 | 30 | var images Images 31 | imageNames := gcrImagesQuery(ctx, gcr.namespace, gcr.queryLimit, 5) 32 | for _, n := range imageNames { 33 | ss := strings.Split(n, ":") 34 | if len(ss) != 2 { 35 | logrus.Errorf("image name format error: %s", n) 36 | continue 37 | } 38 | if gcr.kubeadm { 39 | images = append(images, &Image{ 40 | Repo: defaultK8sRepo, 41 | Name: strings.TrimPrefix(ss[0], gcr.namespace+"/"), 42 | Tag: ss[1], 43 | }) 44 | } else { 45 | images = append(images, &Image{ 46 | Repo: defaultGcrRepo, 47 | User: gcr.namespace, 48 | Name: strings.TrimPrefix(ss[0], gcr.namespace+"/"), 49 | Tag: ss[1], 50 | }) 51 | } 52 | } 53 | return images 54 | } 55 | 56 | func (gcr *Gcr) Sync(ctx context.Context, opt *SyncOption) { 57 | gcrImages := gcr.setDefault(opt).Images(ctx) 58 | logrus.Infof("sync images count: %d", len(gcrImages)) 59 | imgs := SyncImages(ctx, gcrImages, opt) 60 | report(imgs, opt) 61 | } 62 | 63 | func (gcr *Gcr) setDefault(opt *SyncOption) *Gcr { 64 | gcr.kubeadm = opt.Kubeadm 65 | if opt.QueryLimit == 0 { 66 | gcr.queryLimit = 20 67 | } else { 68 | gcr.queryLimit = opt.QueryLimit 69 | } 70 | gcr.namespace = opt.NameSpace 71 | return gcr 72 | } 73 | 74 | func gcrImagesQuery(ctx context.Context, ns string, queryLimit, maxWait int) []string { 75 | nsCh := make(chan string, 1000) 76 | imagesCh := make(chan string, 1000) 77 | pool, _ := ants.NewPool(queryLimit, ants.WithPreAlloc(true), ants.WithPanicHandler(func(i interface{}) { 78 | logrus.Error(i) 79 | })) 80 | 81 | var images []string 82 | go func() { 83 | for n := range nsCh { 84 | gcrImagesBackgroundQuery(n, nsCh, imagesCh, pool) 85 | } 86 | }() 87 | go func() { 88 | for i := range imagesCh { 89 | images = append(images, i) 90 | } 91 | }() 92 | 93 | nsCh <- ns 94 | waitCount := 0 95 | tick := time.NewTicker(time.Second) 96 | for { 97 | select { 98 | case <-tick.C: 99 | logrus.Infof("gcr query pool running: %d, images count: %d", pool.Running(), len(images)) 100 | if waitCount >= maxWait { 101 | goto done 102 | } 103 | if pool.Running() == 0 { 104 | waitCount++ 105 | } 106 | case <-ctx.Done(): 107 | goto done 108 | } 109 | } 110 | done: 111 | close(nsCh) 112 | close(imagesCh) 113 | pool.Release() 114 | return images 115 | } 116 | 117 | func gcrImagesBackgroundQuery(ns string, nsCh, imagesCh chan<- string, pool *ants.Pool) { 118 | err := pool.Submit(func() { 119 | logrus.Debugf("query gcr ns: %s", ns) 120 | _, body, errs := gorequest.New(). 121 | Timeout(DefaultHTTPTimeout). 122 | Retry(DefaultGoRequestRetry, DefaultGoRequestRetryTime). 123 | Get(fmt.Sprintf(gcrStandardImagesTpl, ns)). 124 | EndBytes() 125 | if len(errs) > 0 { 126 | logrus.Error(errs) 127 | return 128 | } 129 | 130 | var resp GcrResp 131 | err := jsoniter.Unmarshal(body, &resp) 132 | if err != nil { 133 | logrus.Error(err) 134 | return 135 | } 136 | if len(resp.Child) > 0 { 137 | for _, c := range resp.Child { 138 | nsCh <- ns + "/" + c 139 | } 140 | } else { 141 | for _, t := range resp.Tags { 142 | imagesCh <- resp.Name + ":" + t 143 | } 144 | } 145 | }) 146 | if err != nil { 147 | logrus.Error(err) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /core/types.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type Image struct { 9 | Repo string 10 | User string 11 | Name string 12 | Tag string 13 | 14 | Success bool 15 | CacheHit bool 16 | Err error 17 | } 18 | 19 | func (img *Image) String() string { 20 | if img.User != "" { 21 | return fmt.Sprintf("%s/%s/%s:%s", img.Repo, img.User, img.Name, img.Tag) 22 | } 23 | return fmt.Sprintf("%s/%s:%s", img.Repo, img.Name, img.Tag) 24 | } 25 | 26 | func (img *Image) MergeName() string { 27 | if img.User != "" { 28 | return fmt.Sprintf("%s_%s_%s", strings.ReplaceAll(img.Repo, "/", "_"), strings.ReplaceAll(img.User, "/", "_"), strings.ReplaceAll(img.Name, "/", "_")) 29 | } 30 | return fmt.Sprintf("%s_%s", strings.ReplaceAll(img.Repo, "/", "_"), strings.ReplaceAll(img.Name, "/", "_")) 31 | } 32 | 33 | type Images []*Image 34 | 35 | func (imgs Images) Len() int { return len(imgs) } 36 | func (imgs Images) Less(i, j int) bool { return imgs[i].String() < imgs[j].String() } 37 | func (imgs Images) Swap(i, j int) { imgs[i], imgs[j] = imgs[j], imgs[i] } 38 | 39 | type GcrResp struct { 40 | Child []string `json:"child"` 41 | Manifest map[string]GcrManifest `json:"manifest"` 42 | Name string `json:"name"` 43 | Tags []string `json:"tags"` 44 | } 45 | 46 | type GcrManifest struct { 47 | ImageSizeBytes string `json:"imageSizeBytes"` 48 | LayerID string `json:"layerId"` 49 | MediaType string `json:"mediaType"` 50 | Tag []string `json:"tag"` 51 | TimeCreatedMS string `json:"timeCreatedMs"` 52 | TimeUploadedMS string `json:"timeUploadedMs"` 53 | } 54 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mritd/imgsync 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/containers/image/v5 v5.4.4 7 | github.com/elazarl/goproxy v0.0.0-20200426045556-49ad98f6dac1 // indirect 8 | github.com/json-iterator/go v1.1.9 9 | github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 10 | github.com/panjf2000/ants/v2 v2.4.0 11 | github.com/parnurzeal/gorequest v0.2.16 12 | github.com/sirupsen/logrus v1.6.0 13 | github.com/smartystreets/goconvey v1.6.4 // indirect 14 | github.com/spf13/cobra v1.0.0 15 | gopkg.in/tucnak/telebot.v2 v2.0.0-20200426184946-59629fe0483e 16 | moul.io/http2curl v1.0.0 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774/go.mod h1:6/0dYRLLXyJjbkIPeeGyoJ/eKOSI0eU6eTlCBYibgd0= 3 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= 4 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 5 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 6 | github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA= 7 | github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= 8 | github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= 9 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 10 | github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= 11 | github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= 12 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= 13 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= 14 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 15 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 16 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 17 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 18 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 19 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 20 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 21 | github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 22 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 23 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 24 | github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= 25 | github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= 26 | github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= 27 | github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= 28 | github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= 29 | github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= 30 | github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= 31 | github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= 32 | github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= 33 | github.com/containers/image/v5 v5.4.4 h1:JSanNn3v/BMd3o0MEvO4R4OKNuoJUSzVGQAI1+0FMXE= 34 | github.com/containers/image/v5 v5.4.4/go.mod h1:g7cxNXitiLi6pEr9/L9n/0wfazRuhDKXU15kV86N8h8= 35 | github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b h1:Q8ePgVfHDplZ7U33NwHZkrVELsZP5fYj9pM5WBZB2GE= 36 | github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= 37 | github.com/containers/ocicrypt v1.0.2 h1:Q0/IPs8ohfbXNxEfyJ2pFVmvJu5BhqJUAmc6ES9NKbo= 38 | github.com/containers/ocicrypt v1.0.2/go.mod h1:nsOhbP19flrX6rE7ieGFvBlr7modwmNjsqWarIUce4M= 39 | github.com/containers/storage v1.19.1 h1:YKIzOO12iaD5Ra0PKFS6emcygbHLmwmQOCQRU/19YAQ= 40 | github.com/containers/storage v1.19.1/go.mod h1:KbXjSwKnx17ejOsjFcCXSf78mCgZkQSLPBNTMRc3XrQ= 41 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 42 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 43 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 44 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 45 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 46 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 47 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 48 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 49 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 50 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 51 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 52 | github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= 53 | github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 54 | github.com/docker/docker v1.4.2-0.20191219165747-a9416c67da9f h1:Sm8iD2lifO31DwXfkGzq8VgA7rwxPjRsYmeo0K/dF9Y= 55 | github.com/docker/docker v1.4.2-0.20191219165747-a9416c67da9f/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 56 | github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ= 57 | github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= 58 | github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= 59 | github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= 60 | github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= 61 | github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= 62 | github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 63 | github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= 64 | github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= 65 | github.com/elazarl/goproxy v0.0.0-20200426045556-49ad98f6dac1 h1:TEmChtx8+IeOghiySC8kQIr0JZOdKUmRmmkuRDuYs3E= 66 | github.com/elazarl/goproxy v0.0.0-20200426045556-49ad98f6dac1/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= 67 | github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= 68 | github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= 69 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 70 | github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa h1:RDBNVkRviHZtvDvId8XSGPu3rmpmSe+wKRcEWNgsfWU= 71 | github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= 72 | github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= 73 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 74 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 75 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 76 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 77 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 78 | github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= 79 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 80 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 81 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 82 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 83 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 84 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 85 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 86 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 87 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 88 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 89 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 90 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 91 | github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= 92 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 93 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 94 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 95 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 96 | github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= 97 | github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 98 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 99 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 100 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 101 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 102 | github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 103 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 104 | github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= 105 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 106 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 107 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 108 | github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= 109 | github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 110 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 111 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 112 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 113 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 114 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 115 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= 116 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 117 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 118 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 119 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 120 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 121 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 122 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 123 | github.com/klauspost/compress v1.10.5 h1:7q6vHIqubShURwQz8cQK6yIe/xC3IF0Vm7TGfqjewrc= 124 | github.com/klauspost/compress v1.10.5/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= 125 | github.com/klauspost/pgzip v1.2.3 h1:Ce2to9wvs/cuJ2b86/CKQoTYr9VHfpanYosZ0UBJqdw= 126 | github.com/klauspost/pgzip v1.2.3/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 127 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 128 | github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= 129 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 130 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 131 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 132 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 133 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 134 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 135 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 136 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 137 | github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= 138 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 139 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 140 | github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= 141 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 142 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 143 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 144 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 145 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 146 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 147 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 148 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 149 | github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 150 | github.com/mtrmac/gpgme v0.1.2 h1:dNOmvYmsrakgW7LcgiprD0yfRuQQe8/C8F6Z+zogO3s= 151 | github.com/mtrmac/gpgme v0.1.2/go.mod h1:GYYHnGSuS7HK3zVS2n3y73y0okK/BeKzwnn5jgiVFNI= 152 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 153 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 154 | github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= 155 | github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= 156 | github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= 157 | github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= 158 | github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= 159 | github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 h1:yN8BPXVwMBAm3Cuvh1L5XE8XpvYRMdsVLd82ILprhUU= 160 | github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= 161 | github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= 162 | github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= 163 | github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= 164 | github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= 165 | github.com/opencontainers/selinux v1.5.1/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= 166 | github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913/go.mod h1:J6OG6YJVEWopen4avK3VNQSnALmmjvniMmni/YFYAwc= 167 | github.com/panjf2000/ants/v2 v2.4.0 h1:embKPQeNWMRbnrRKURv4TXJwjQRWMEAfqZT6Pe5hZNc= 168 | github.com/panjf2000/ants/v2 v2.4.0/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A= 169 | github.com/parnurzeal/gorequest v0.2.16 h1:T/5x+/4BT+nj+3eSknXmCTnEVGSzFzPGdpqmUVVZXHQ= 170 | github.com/parnurzeal/gorequest v0.2.16/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE= 171 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 172 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 173 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 174 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 175 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 176 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 177 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 178 | github.com/pquerna/ffjson v0.0.0-20181028064349-e517b90714f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= 179 | github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= 180 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 181 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 182 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 183 | github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= 184 | github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= 185 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 186 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= 187 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 188 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 189 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 190 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 191 | github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= 192 | github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= 193 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 194 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 195 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 196 | github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= 197 | github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= 198 | github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= 199 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 200 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 201 | github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= 202 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 203 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 204 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 205 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 206 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 207 | github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= 208 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 209 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= 210 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 211 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= 212 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 213 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 214 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 215 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 216 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 217 | github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= 218 | github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= 219 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 220 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 221 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 222 | github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= 223 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 224 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 225 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 226 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 227 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 228 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= 229 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 230 | github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= 231 | github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= 232 | github.com/tchap/go-patricia v2.3.0+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= 233 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 234 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 235 | github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4= 236 | github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 237 | github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 238 | github.com/vbatts/tar-split v0.11.1/go.mod h1:LEuURwDEiWjRjwu46yU3KVGuUdVv/dcnpcEPSzR8z6g= 239 | github.com/vbauerster/mpb/v5 v5.0.4 h1:w7l/tJfHmtIOKZkU+bhbDZOUxj1kln9jy4DUOp3Tl14= 240 | github.com/vbauerster/mpb/v5 v5.0.4/go.mod h1:fvzasBUyuo35UyuA6sSOlVhpLoNQsp2nBdHw7OiSUU8= 241 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 242 | github.com/xeipuuv/gojsonpointer v0.0.0-20190809123943-df4f5c81cb3b h1:6cLsL+2FW6dRAdl5iMtHgRogVCff0QpRi9653YmdcJA= 243 | github.com/xeipuuv/gojsonpointer v0.0.0-20190809123943-df4f5c81cb3b/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 244 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= 245 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= 246 | github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= 247 | github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= 248 | github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= 249 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 250 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 251 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 252 | go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= 253 | go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= 254 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 255 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 256 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 257 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 258 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 259 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 260 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 261 | golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 h1:Q7tZBpemrlsc2I7IyODzhtallWRSm4Q0d09pL6XbQtU= 262 | golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 263 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 264 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 265 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 266 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 267 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 268 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 269 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 270 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 271 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 272 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 273 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 274 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 275 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 276 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 277 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 278 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= 279 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 280 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 281 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 282 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 283 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 284 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 285 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 286 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= 287 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 288 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 289 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 290 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 291 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 292 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 293 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 294 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 295 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 296 | golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 297 | golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 298 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 299 | golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 300 | golang.org/x/sys v0.0.0-20191127021746-63cb32ae39b2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 301 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 302 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 303 | golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8= 304 | golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 305 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 306 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 307 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 308 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 309 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 310 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 311 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 312 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 313 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 314 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 315 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 316 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 317 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 318 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 319 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 320 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 321 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 322 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 323 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 324 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 325 | google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= 326 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 327 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 328 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 329 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 330 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 331 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 332 | gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4= 333 | gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 334 | gopkg.in/tucnak/telebot.v2 v2.0.0-20200426184946-59629fe0483e h1:b9xwpngyOzAgWsFzw+kknHd/XZAnEsohHcGfu+z/LZA= 335 | gopkg.in/tucnak/telebot.v2 v2.0.0-20200426184946-59629fe0483e/go.mod h1:+2HaHCMjzfvC3MVOSmgRKeAPruYl4PEcSxywvP8GipU= 336 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 337 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 338 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 339 | gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 340 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 341 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 342 | gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= 343 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 344 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 345 | k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= 346 | moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8= 347 | moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE= 348 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/mritd/imgsync/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /version: -------------------------------------------------------------------------------- 1 | v2.1.1 --------------------------------------------------------------------------------