├── .envrc ├── .github └── workflows │ ├── linter.yml │ └── release.yaml ├── .gitignore ├── .golangci.yaml ├── .goreleaser.yml ├── CONTRIBUTING.md ├── Dockerfile.release ├── LICENSE ├── Makefile ├── README.md ├── cmd └── syno-cli │ ├── commands │ ├── base.go │ ├── certs_auto.go │ ├── certs_delete.go │ ├── certs_ls.go │ ├── certs_upload.go │ ├── ds_create.go │ └── ds_ls.go │ └── main.go ├── go.mod ├── go.sum └── pkg └── client ├── base.go ├── base_test.go ├── certs.go ├── certs_test.go ├── download_station.go ├── download_station_enum.go ├── download_station_test.go └── utils.go /.envrc: -------------------------------------------------------------------------------- 1 | source_up 2 | 3 | if [ -f .env ]; then 4 | dotenv .env 5 | fi -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | pull_request: 4 | permissions: 5 | contents: read 6 | jobs: 7 | golangci: 8 | name: lint 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: golangci-lint 13 | uses: golangci/golangci-lint-action@v2 14 | with: 15 | version: v1.43 -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Build and release 2 | on: 3 | push: 4 | tags: 5 | - 'v*' 6 | 7 | env: 8 | REGISTRY: ghcr.io 9 | 10 | jobs: 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Log in to the Container registry 16 | uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 17 | with: 18 | registry: ${{ env.REGISTRY }} 19 | username: ${{ github.actor }} 20 | password: ${{ secrets.GITHUB_TOKEN }} 21 | - name: Log in to the main registry 22 | uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 23 | with: 24 | username: reddec 25 | password: ${{ secrets.DOCKERIO_PASSWORD }} 26 | - name: Set up Go 27 | uses: actions/setup-go@v4 28 | with: 29 | go-version: '^1.23' 30 | id: go 31 | - name: Set up QEMU 32 | uses: docker/setup-qemu-action@v1 33 | - name: Set up Docker Buildx 34 | id: buildx 35 | uses: docker/setup-buildx-action@v1 36 | - name: Check out code into the Go module directory 37 | uses: actions/checkout@v3 38 | with: 39 | lfs: true 40 | fetch-depth: 0 41 | - name: Checkout LFS objects 42 | run: git lfs checkout 43 | - name: Pull tag 44 | run: git fetch --tags 45 | - name: Run GoReleaser 46 | uses: goreleaser/goreleaser-action@v4 47 | with: 48 | version: latest 49 | args: release --clean 50 | env: 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | test-data 3 | .idea 4 | /dist 5 | /build 6 | .cache 7 | .env.local 8 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | run: 2 | tests: false 3 | issues: 4 | exclude-dirs: 5 | - cmd 6 | - dist 7 | - test-data 8 | linters: 9 | disable: 10 | - lll 11 | - gci 12 | - wsl 13 | - godot 14 | - tagliatelle 15 | - exhaustruct 16 | - nakedret 17 | - gofumpt 18 | - cyclop 19 | - wrapcheck 20 | - varnamelen 21 | - nlreturn 22 | - gomnd 23 | enable-all: true 24 | linters-settings: 25 | funlen: 26 | lines: 80 27 | statements: 50 28 | gocognit: 29 | min-complexity: 90 30 | gocyclo: 31 | min-complexity: 50 -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: syno-cli 2 | builds: 3 | - env: 4 | - CGO_ENABLED=0 5 | goos: 6 | - linux 7 | - windows 8 | - darwin 9 | goarch: 10 | - arm64 11 | - amd64 12 | flags: 13 | - -trimpath 14 | main: ./cmd/syno-cli 15 | checksum: 16 | name_template: 'checksums.txt' 17 | snapshot: 18 | name_template: "{{ incpatch .Version }}-next" 19 | dockers: 20 | - image_templates: 21 | - "ghcr.io/reddec/{{ .ProjectName }}:{{ .Version }}-amd64" 22 | - "reddec/{{ .ProjectName }}:{{ .Version }}-amd64" 23 | use: buildx 24 | dockerfile: Dockerfile.release 25 | build_flag_templates: 26 | - "--platform=linux/amd64" 27 | - image_templates: 28 | - "ghcr.io/reddec/{{ .ProjectName }}:{{ .Version }}-arm64v8" 29 | - "reddec/{{ .ProjectName }}:{{ .Version }}-arm64v8" 30 | use: buildx 31 | goarch: arm64 32 | dockerfile: Dockerfile.release 33 | build_flag_templates: 34 | - "--platform=linux/arm64/v8" 35 | docker_manifests: 36 | - name_template: "ghcr.io/reddec/{{ .ProjectName }}:{{ .Version }}" 37 | image_templates: 38 | - "ghcr.io/reddec/{{ .ProjectName }}:{{ .Version }}-amd64" 39 | - "ghcr.io/reddec/{{ .ProjectName }}:{{ .Version }}-arm64v8" 40 | - name_template: "reddec/{{ .ProjectName }}:{{ .Version }}" 41 | image_templates: 42 | - "reddec/{{ .ProjectName }}:{{ .Version }}-amd64" 43 | - "reddec/{{ .ProjectName }}:{{ .Version }}-arm64v8" 44 | release: 45 | footer: | 46 | ## Docker images 47 | 48 | ghcr.io/reddec/{{ .ProjectName }}:{{ .Version }} 49 | reddec/{{ .ProjectName }}:{{ .Version }} 50 | 51 | Both images supports `arm64` and `amd64` and built on top of alpine 52 | 53 | changelog: 54 | sort: asc 55 | filters: 56 | exclude: 57 | - '^docs:' 58 | - '^test:' 59 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Dev environment 2 | 3 | Requires: 4 | 5 | - Synology NAS with DSM 7+ 6 | - Go 1.23+ 7 | - Make 8 | - [Linter](https://golangci-lint.run/) 1.61.0 + 9 | 10 | Place in `.env` configuration 11 | 12 | ```env 13 | SYNOLOGY_URL= 14 | SYNOLOGY_USER= 15 | SYNOLOGY_PASSWORD= 16 | ``` 17 | 18 | you may use [direnv](https://direnv.net/) for automatic load 19 | 20 | it `.gitignore`'d and automatically loaded in tests. 21 | 22 | 23 | ### Generate test certificates 24 | 25 | make gen-certs 26 | 27 | 28 | ### Generate code 29 | 30 | make generate 31 | 32 | ### Run linters 33 | 34 | make lint -------------------------------------------------------------------------------- /Dockerfile.release: -------------------------------------------------------------------------------- 1 | FROM alpine:3.20 2 | ENV SYNOLOGY_URL=http://172.17.0.1:5000 \ 3 | CACHE_DIR=/var/syno \ 4 | PROVIDER=\ 5 | DOMAINS= 6 | VOLUME /var/syno 7 | ENTRYPOINT ["/bin/syno-cli"] 8 | 9 | ADD syno-cli /bin/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 reddec 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. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | lint: 2 | golangci-lint run 3 | 4 | snapshot: 5 | goreleaser --rm-dist --snapshot 6 | 7 | local: 8 | rm -rf dist 9 | mkdir -p dist 10 | CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o dist/syno-cli ./cmd/syno-cli 11 | cp Dockerfile.release dist/Dockerfile 12 | cd dist && docker build -t syno-cli . 13 | 14 | # Generate test certs 15 | # https://github.com/FiloSottile/mkcert 16 | gen-certs: test-data 17 | mkdir -p test-data 18 | cd test-data && CAROOT=. go run filippo.io/mkcert@v1.4.4 example.com 19 | 20 | 21 | # Generate go code 22 | generate: 23 | go generate ./... 24 | 25 | test: 26 | go test -v ./... 27 | 28 | .PHONY: generate test -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Synology CLI 2 | 3 | Unofficial wrapper over Synology API in Go. 4 | 5 | Focus on administrative tasks. 6 | 7 | * Tutorial for [automatic SSL certificates on NAS](https://reddec.net/articles/how-to-get-ssl-on-synology/) 8 | 9 | > [!TIP] 10 | > It does support creating tasks in Download Station using files (torrent, nzb, urls); though it uses undocumented API. 11 | 12 | 13 | Supports: 14 | 15 | * X86-64 and ARM [builds](https://github.com/reddec/syno-cli/releases/latest) 16 | * Universal docker image (arm and amd64) 17 | * As [CLI](https://github.com/reddec/syno-cli/releases/latest) and 18 | as [library](https://pkg.go.dev/github.com/reddec/syno-cli) 19 | 20 | ## Development notes 21 | 22 | - Stable are only tags prefixed by `v` (ex: `v0.1.4`) 23 | 24 | ## Installation 25 | 26 | * Pre-built binaries from [releases](https://github.com/reddec/syno-cli/releases/latest) 27 | * Docker (universal): `ghcr.io/reddec/syno-cli:` ( 28 | see [releases](https://github.com/reddec/syno-cli/releases/latest)) 29 | * From source (requires latest Go): `go install github.com/reddec/syno-cli/cmd/syno-cli@latest` 30 | 31 | ## Usage 32 | 33 | Each command supports `--help` option. 34 | 35 | ### main 36 | 37 | ``` 38 | Usage: 39 | syno-cli [OPTIONS] 40 | 41 | Unofficial CLI for Synology DSM 42 | Author: Aleksandr Baryshnikov 43 | 44 | Help Options: 45 | -h, --help Show this help message 46 | 47 | Available commands: 48 | cert manager certificates (aliases: certificates, certificate, certs, cert, c) 49 | ds download station (aliases: download-station, download, dl, d) 50 | ``` 51 | 52 | ### cert 53 | 54 | ``` 55 | Usage: 56 | syno-cli [OPTIONS] cert 57 | 58 | Help Options: 59 | -h, --help Show this help message 60 | 61 | Available commands: 62 | auto automatically issue and push certificates (aliases: dns01, lego, a) 63 | delete delete certificate (aliases: remove, rm, del, d) 64 | list list certificates (aliases: ls, l) 65 | upload upload certificate (aliases: up, u) 66 | ``` 67 | 68 | ### automatic certs 69 | 70 | ``` 71 | Usage: 72 | syno-cli [OPTIONS] cert auto [auto-OPTIONS] [domain...] 73 | 74 | Help Options: 75 | -h, --help Show this help message 76 | 77 | [auto command options] 78 | -c, --cache-dir= Cache location for accounts information (default: .cache) [$CACHE_DIR] 79 | -r, --renew-before= Renew certificate time reserve (default: 720h) [$RENEW_BEFORE] 80 | -e, --email= Email for contact [$EMAIL] 81 | -p, --provider= DNS challenge provider [$PROVIDER] 82 | -D, --dns= Custom resolvers (default: 8.8.8.8) [$DNS] 83 | -t, --timeout= DNS challenge timeout (default: 1m) [$TIMEOUT] 84 | -d, --domains= Domains names to issue [$DOMAINS] 85 | 86 | Synology Client: 87 | --synology.user= Synology username [$SYNOLOGY_USER] 88 | --synology.password= Synology password [$SYNOLOGY_PASSWORD] 89 | --synology.url= Synology URL (default: http://localhost:5000) [$SYNOLOGY_URL] 90 | --synology.insecure Disable TLS (HTTPS) verification [$SYNOLOGY_INSECURE] 91 | ``` 92 | 93 | ## Download station 94 | 95 | In progress. Already supports creating task from files. 96 | 97 | API supported: 98 | 99 | - create task 100 | - list tasks 101 | 102 | Command: `syno-cli ds ...` 103 | 104 | ``` 105 | Usage: 106 | syno-cli [OPTIONS] ds 107 | 108 | Help Options: 109 | -h, --help Show this help message 110 | 111 | Available commands: 112 | create create task (aliases: add, new, c) 113 | list list tasks (aliases: ls, l) 114 | ``` 115 | 116 | ### Create download task 117 | 118 | ``` 119 | Usage: 120 | syno-cli [OPTIONS] ds create [create-OPTIONS] [ref] 121 | 122 | Help Options: 123 | -h, --help Show this help message 124 | 125 | [create command options] 126 | --debug Enable debug logging [$DEBUG] 127 | -f, --format=[torrent|txt|nzb|auto] File format (default: auto) [$FORMAT] 128 | -d, --destination= Destination directory (default: Downloads) [$DESTINATION] 129 | 130 | Synology Client: 131 | --synology.user= Synology username [$SYNOLOGY_USER] 132 | --synology.password= Synology password [$SYNOLOGY_PASSWORD] 133 | --synology.url= Synology URL (default: http://localhost:5000) [$SYNOLOGY_URL] 134 | --synology.insecure Disable TLS (HTTPS) verification [$SYNOLOGY_INSECURE] 135 | --synology.timeout= Default timeout (default: 30s) [$SYNOLOGY_TIMEOUT] 136 | 137 | [create command arguments] 138 | ref: URL or file name. If not set or set to - (dash) - STDIN will be used 139 | ``` 140 | 141 | - If `ref` is set it could be URL, including magnet or path to file. 142 | 143 | ### List tasks 144 | 145 | ``` 146 | Usage: 147 | syno-cli [OPTIONS] ds list [list-OPTIONS] 148 | 149 | Help Options: 150 | -h, --help Show this help message 151 | 152 | [list command options] 153 | --debug Enable debug logging [$DEBUG] 154 | -f, --format=[table|json] How to show output (default: table) [$FORMAT] 155 | -o, --offset= Offset (default: 0) [$OFFSET] 156 | -l, --limit= Max number of items (default: 1000) [$LIMIT] 157 | 158 | Synology Client: 159 | --synology.user= Synology username [$SYNOLOGY_USER] 160 | --synology.password= Synology password [$SYNOLOGY_PASSWORD] 161 | --synology.url= Synology URL (default: http://localhost:5000) [$SYNOLOGY_URL] 162 | --synology.insecure Disable TLS (HTTPS) verification [$SYNOLOGY_INSECURE] 163 | --synology.timeout= Default timeout (default: 30s) [$SYNOLOGY_TIMEOUT] 164 | ``` 165 | -------------------------------------------------------------------------------- /cmd/syno-cli/commands/base.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "crypto/tls" 5 | "log/slog" 6 | "net/http" 7 | "net/http/cookiejar" 8 | "os" 9 | "time" 10 | 11 | "github.com/reddec/syno-cli/pkg/client" 12 | ) 13 | 14 | type SynoClient struct { 15 | User string `long:"user" env:"USER" description:"Synology username" required:"true"` 16 | Password string `long:"password" env:"PASSWORD" description:"Synology password" required:"true"` 17 | URL string `long:"url" env:"URL" description:"Synology URL" default:"http://localhost:5000"` 18 | Insecure bool `long:"insecure" env:"INSECURE" description:"Disable TLS (HTTPS) verification"` 19 | Timeout time.Duration `long:"timeout" env:"TIMEOUT" description:"Default timeout" default:"30s"` 20 | } 21 | 22 | func (sc SynoClient) Client() *client.Client { 23 | jar, err := cookiejar.New(nil) 24 | if err != nil { 25 | panic(err) // impossible 26 | } 27 | var httpClient = &http.Client{ 28 | Jar: jar, 29 | Timeout: sc.Timeout, 30 | } 31 | if sc.Insecure { 32 | httpClient.Transport = &http.Transport{ 33 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 34 | } 35 | } 36 | return client.New(client.Config{ 37 | Client: httpClient, 38 | User: sc.User, 39 | Password: sc.Password, 40 | URL: sc.URL, 41 | }) 42 | } 43 | 44 | const ( 45 | fmtJSON = "json" 46 | fmtTable = "table" 47 | ) 48 | 49 | type Logging struct { 50 | Debug bool `long:"debug" env:"DEBUG" description:"Enable debug logging"` 51 | } 52 | 53 | func (l *Logging) SetupLogging() { 54 | lvl := new(slog.LevelVar) 55 | 56 | logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ 57 | Level: lvl, 58 | })) 59 | if l.Debug { 60 | lvl.Set(slog.LevelDebug) 61 | } else { 62 | lvl.Set(slog.LevelInfo) 63 | } 64 | 65 | slog.SetDefault(logger) 66 | } 67 | -------------------------------------------------------------------------------- /cmd/syno-cli/commands/certs_auto.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto" 7 | "crypto/ecdsa" 8 | "crypto/elliptic" 9 | "crypto/rand" 10 | "crypto/x509" 11 | "encoding/json" 12 | "encoding/pem" 13 | "errors" 14 | "fmt" 15 | "log/slog" 16 | "os" 17 | "os/signal" 18 | "path/filepath" 19 | "time" 20 | 21 | "github.com/go-acme/lego/v4/certificate" 22 | "github.com/go-acme/lego/v4/challenge/dns01" 23 | "github.com/go-acme/lego/v4/lego" 24 | "github.com/go-acme/lego/v4/providers/dns" 25 | "github.com/go-acme/lego/v4/registration" 26 | 27 | "github.com/reddec/syno-cli/pkg/client" 28 | ) 29 | 30 | //nolint:staticcheck 31 | type CertsAuto struct { 32 | SynoClient `group:"Synology Client" namespace:"synology" env-namespace:"SYNOLOGY"` 33 | CacheDir string `short:"c" long:"cache-dir" env:"CACHE_DIR" description:"Cache location for accounts information" default:".cache"` 34 | RenewBefore time.Duration `short:"r" long:"renew-before" env:"RENEW_BEFORE" description:"Renew certificate time reserve" default:"720h"` 35 | Email string `short:"e" long:"email" env:"EMAIL" description:"Email for contact"` 36 | Provider string `short:"p" long:"provider" env:"PROVIDER" description:"DNS challenge provider" required:"true" ` 37 | DNS []string `short:"D" long:"dns" env:"DNS" env-delim:"," description:"Custom resolvers" default:"8.8.8.8"` 38 | Timeout time.Duration `short:"t" long:"timeout" env:"TIMEOUT" description:"DNS challenge timeout" default:"1m"` 39 | Domains []string `short:"d" long:"domains" env:"DOMAINS" env-delim:"," description:"Domains names to issue" required:"true"` 40 | } 41 | 42 | func (lc *CertsAuto) Execute([]string) error { 43 | ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) 44 | defer cancel() 45 | 46 | account, err := lc.getOrCreateAccount() 47 | if err != nil { 48 | return err 49 | } 50 | 51 | slog.Info("setting challenger", "provider", lc.Provider) 52 | if err := lc.setupChallenge(account); err != nil { 53 | return err 54 | } 55 | 56 | slog.Info("start initial setup") 57 | 58 | for { 59 | slog.Info("issuing or renewing certificates if needed") 60 | if list, err := lc.issueOrRenewCerts(ctx, account); err != nil { 61 | slog.Error("failed issue certs", "error", err) 62 | } else if err := lc.pushToSynology(ctx, list); err != nil { 63 | slog.Error("failed push to Synology", "error", err) 64 | } 65 | slog.Info("done, next check after 1 hour") 66 | select { 67 | case <-ctx.Done(): 68 | return nil 69 | case <-time.After(time.Hour): 70 | } 71 | } 72 | } 73 | 74 | func (lc *CertsAuto) issueOrRenewCerts(ctx context.Context, lgc *lego.Client) ([]*certificate.Resource, error) { 75 | var certs []*certificate.Resource 76 | for _, domain := range lc.Domains { 77 | cert, err := loadCert(lc.CacheDir, domain) 78 | if errors.Is(err, os.ErrNotExist) { 79 | slog.Info("issuing new certificate", "domain", domain, "reason", "no certificate") 80 | // issue 81 | cert, err = lc.issueCert(domain, lgc) 82 | if err != nil { 83 | return nil, fmt.Errorf("issue new certificate for %s: %w", domain, err) 84 | } 85 | } else if err != nil { 86 | // something happen during load 87 | return certs, err 88 | } else if crt, err := parseCert(cert); err != nil { 89 | // parsing failed 90 | return certs, err 91 | } else if time.Now().After(crt.NotAfter) { 92 | slog.Info("issuing new certificate", "domain", domain, "reason", "the old one expired") 93 | // expired 94 | // issue 95 | cert, err = lc.issueCert(domain, lgc) 96 | if err != nil { 97 | return nil, fmt.Errorf("issue new certificate for %s: %w", domain, err) 98 | } 99 | } else if time.Until(crt.NotAfter) <= lc.RenewBefore { 100 | // renew if soon expired 101 | cert, err = lc.renewCert(cert, lgc) 102 | if err != nil { 103 | return nil, fmt.Errorf("issue new certificate for %s: %w", domain, err) 104 | } 105 | } 106 | certs = append(certs, cert) 107 | select { 108 | case <-ctx.Done(): 109 | return nil, ctx.Err() 110 | default: 111 | } 112 | } 113 | 114 | return certs, nil 115 | } 116 | 117 | func (lc *CertsAuto) pushToSynology(ctx context.Context, certs []*certificate.Resource) error { 118 | syno := lc.SynoClient.Client() 119 | 120 | knownCerts, err := syno.ListCerts(ctx) 121 | if err != nil { 122 | return err 123 | } 124 | 125 | var index = make(map[string]client.Certificate) 126 | for _, c := range knownCerts { 127 | index[c.Description] = c 128 | } 129 | 130 | for _, res := range certs { 131 | slog.Info("pushing certs to Synology", "domain", res.Domain) 132 | status, err := syno.UploadCert(ctx, client.NewCertificate{ 133 | Name: res.Domain, 134 | Cert: bytes.NewReader(res.Certificate), 135 | CA: bytes.NewReader(res.IssuerCertificate), 136 | Key: bytes.NewReader(res.PrivateKey), 137 | }) 138 | if err != nil { 139 | return fmt.Errorf("push to synology for domain %s: %w", res.Domain, err) 140 | } 141 | slog.Info("certificate uploaded", "certificate_id", status.CertificateID, "server_restarted", status.ServerRestarted) 142 | } 143 | 144 | return nil 145 | } 146 | 147 | func (lc *CertsAuto) issueCert(domain string, lgc *lego.Client) (*certificate.Resource, error) { 148 | request, err := lgc.Certificate.Obtain(certificate.ObtainRequest{ 149 | Domains: []string{domain}, 150 | Bundle: true, 151 | }) 152 | if err != nil { 153 | return nil, fmt.Errorf("create certificate request for: %w", err) 154 | } 155 | return request, saveCert(lc.CacheDir, request) 156 | } 157 | 158 | func (lc *CertsAuto) renewCert(res *certificate.Resource, lgc *lego.Client) (*certificate.Resource, error) { 159 | ng, err := lgc.Certificate.Renew(*res, true, false, "") 160 | if err != nil { 161 | return nil, err 162 | } 163 | return ng, saveCert(lc.CacheDir, ng) 164 | } 165 | 166 | func (lc *CertsAuto) setupChallenge(lgc *lego.Client) error { 167 | provider, err := dns.NewDNSChallengeProviderByName(lc.Provider) 168 | if err != nil { 169 | return err 170 | } 171 | 172 | var opts = []dns01.ChallengeOption{ 173 | dns01.AddDNSTimeout(lc.Timeout), 174 | } 175 | if len(lc.DNS) > 0 { 176 | opts = append(opts, dns01.AddRecursiveNameservers(lc.DNS)) 177 | } 178 | return lgc.Challenge.SetDNS01Provider(provider, opts...) 179 | } 180 | 181 | func (lc *CertsAuto) getOrCreateAccount() (*lego.Client, error) { 182 | accountFile := filepath.Join(lc.CacheDir, lc.Email+".json") 183 | account, err := loadAccount(accountFile) 184 | if err == nil { 185 | slog.Info("we are using saved account") 186 | return lego.NewClient(lego.NewConfig(account)) 187 | } 188 | 189 | if !errors.Is(err, os.ErrNotExist) { 190 | return nil, err 191 | } 192 | slog.Info("generating new account") 193 | 194 | privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 195 | if err != nil { 196 | return nil, err 197 | } 198 | 199 | user := &legoAccount{ 200 | Email: lc.Email, 201 | Key: privateKey, 202 | } 203 | 204 | config := lego.NewConfig(user) 205 | 206 | client, err := lego.NewClient(config) 207 | if err != nil { 208 | return nil, err 209 | } 210 | reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) 211 | if err != nil { 212 | return nil, err 213 | } 214 | 215 | user.Registration = reg 216 | 217 | return client, user.Save(accountFile) 218 | } 219 | 220 | type legoAccount struct { 221 | Email string 222 | Registration *registration.Resource 223 | Key *ecdsa.PrivateKey `json:"-"` 224 | } 225 | 226 | func (la *legoAccount) GetEmail() string { 227 | return la.Email 228 | } 229 | 230 | func (la *legoAccount) GetRegistration() *registration.Resource { 231 | return la.Registration 232 | } 233 | 234 | func (la *legoAccount) GetPrivateKey() crypto.PrivateKey { 235 | return la.Key 236 | } 237 | 238 | func (la *legoAccount) MarshalJSON() ([]byte, error) { 239 | data, err := x509.MarshalECPrivateKey(la.Key) 240 | if err != nil { 241 | return nil, err 242 | } 243 | 244 | return json.Marshal(serializedLegoAccount{ 245 | Email: la.Email, 246 | Registration: la.Registration, 247 | RawKey: data, 248 | }) 249 | } 250 | 251 | func (la *legoAccount) UnmarshalJSON(bytes []byte) error { 252 | var acc serializedLegoAccount 253 | err := json.Unmarshal(bytes, &acc) 254 | if err != nil { 255 | return err 256 | } 257 | 258 | key, err := x509.ParseECPrivateKey(acc.RawKey) 259 | if err != nil { 260 | return err 261 | } 262 | la.Email = acc.Email 263 | la.Key = key 264 | la.Registration = acc.Registration 265 | return nil 266 | } 267 | 268 | func (la *legoAccount) Save(file string) error { 269 | return atomicJSON(file, la) 270 | } 271 | 272 | func loadAccount(file string) (*legoAccount, error) { 273 | f, err := os.Open(file) 274 | if err != nil { 275 | return nil, err 276 | } 277 | defer f.Close() 278 | var acc legoAccount 279 | 280 | return &acc, json.NewDecoder(f).Decode(&acc) 281 | } 282 | 283 | type serializedLegoAccount struct { 284 | Email string 285 | Registration *registration.Resource 286 | RawKey []byte 287 | } 288 | 289 | func saveCert(dir string, resource *certificate.Resource) error { 290 | return atomicJSON(filepath.Join(dir, resource.Domain+".json"), serializedCertificate{ 291 | Domain: resource.Domain, 292 | CertURL: resource.CertURL, 293 | CertStableURL: resource.CertStableURL, 294 | PrivateKey: resource.PrivateKey, 295 | Certificate: resource.Certificate, 296 | IssuerCertificate: resource.IssuerCertificate, 297 | CSR: resource.CSR, 298 | }) 299 | } 300 | 301 | func loadCert(dir, domain string) (*certificate.Resource, error) { 302 | f, err := os.Open(filepath.Join(dir, domain+".json")) 303 | if err != nil { 304 | return nil, err 305 | } 306 | defer f.Close() 307 | var resource serializedCertificate 308 | err = json.NewDecoder(f).Decode(&resource) 309 | if err != nil { 310 | return nil, err 311 | } 312 | 313 | return &certificate.Resource{ 314 | Domain: resource.Domain, 315 | CertURL: resource.CertURL, 316 | CertStableURL: resource.CertStableURL, 317 | PrivateKey: resource.PrivateKey, 318 | Certificate: resource.Certificate, 319 | IssuerCertificate: resource.IssuerCertificate, 320 | CSR: resource.CSR, 321 | }, nil 322 | } 323 | 324 | type serializedCertificate struct { 325 | Domain string `json:"domain"` 326 | CertURL string `json:"certUrl"` 327 | CertStableURL string `json:"certStableUrl"` 328 | PrivateKey []byte `json:"privateKey"` 329 | Certificate []byte `json:"certificate"` 330 | IssuerCertificate []byte `json:"issuer_certificate"` 331 | CSR []byte `json:"csr"` 332 | } 333 | 334 | func atomicJSON(file string, data interface{}) error { 335 | if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil { 336 | return err 337 | } 338 | tempFile := file + ".tmp" 339 | f, err := os.Create(tempFile) 340 | if err != nil { 341 | return err 342 | } 343 | defer os.Remove(tempFile) 344 | defer f.Close() 345 | enc := json.NewEncoder(f) 346 | enc.SetIndent("", " ") 347 | err = enc.Encode(data) 348 | if err != nil { 349 | return err 350 | } 351 | err = f.Close() 352 | if err != nil { 353 | return err 354 | } 355 | return os.Rename(tempFile, file) 356 | } 357 | 358 | func parseCert(cert *certificate.Resource) (*x509.Certificate, error) { 359 | info, _ := pem.Decode(cert.Certificate) 360 | return x509.ParseCertificate(info.Bytes) 361 | } 362 | -------------------------------------------------------------------------------- /cmd/syno-cli/commands/certs_delete.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | "os/signal" 9 | "text/tabwriter" 10 | 11 | "github.com/reddec/syno-cli/pkg/client" 12 | ) 13 | 14 | //nolint:staticcheck 15 | type CertsDelete struct { 16 | SynoClient `group:"Synology Client" namespace:"synology" env-namespace:"SYNOLOGY"` 17 | Format string `short:"f" long:"format" env:"FORMAT" description:"Output format" default:"table" choice:"table" choice:"json"` 18 | Args struct { 19 | ID string `positional-arg-name:"id" env:"NAME" description:"certificate ID or name" required:"true"` 20 | } `positional-args:"true"` 21 | } 22 | 23 | func (lc *CertsDelete) Execute([]string) error { 24 | ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) 25 | defer cancel() 26 | 27 | syno := lc.Client() 28 | 29 | list, err := syno.ListCerts(ctx) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | var certID string 35 | for _, c := range list { 36 | if c.ID == lc.Args.ID { 37 | certID = c.ID 38 | break 39 | } else if c.Description == lc.Args.ID { 40 | certID = c.ID 41 | } 42 | } 43 | 44 | if certID == "" { 45 | return fmt.Errorf("unknown name or id") //nolint:goerr113 46 | } 47 | 48 | info, err := syno.DeleteCertByID(ctx, certID) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | return lc.show(certID, info) 54 | } 55 | 56 | //nolint:gomnd 57 | func (lc *CertsDelete) show(id string, info *client.ServerStatus) error { 58 | switch lc.Format { 59 | case fmtJSON: 60 | enc := json.NewEncoder(os.Stdout) 61 | enc.SetIndent("", " ") 62 | return enc.Encode(info) 63 | case fmtTable: 64 | fallthrough 65 | default: 66 | tw := tabwriter.NewWriter(os.Stdout, 3, 4, 2, ' ', 0) 67 | _, _ = fmt.Fprintln(tw, 68 | "ID", "\t", 69 | "Server restarted", "\t", 70 | ) 71 | _, _ = fmt.Fprintln(tw, 72 | id, "\t", 73 | info.ServerRestarted, "\t", 74 | ) 75 | return tw.Flush() 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /cmd/syno-cli/commands/certs_ls.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | "os/signal" 9 | "strings" 10 | "text/tabwriter" 11 | "time" 12 | 13 | "github.com/reddec/syno-cli/pkg/client" 14 | ) 15 | 16 | //nolint:staticcheck 17 | type CertsList struct { 18 | SynoClient `group:"Synology Client" namespace:"synology" env-namespace:"SYNOLOGY"` 19 | Format string `short:"f" long:"format" env:"FORMAT" description:"How to show output" default:"table" choice:"table" choice:"json"` 20 | } 21 | 22 | func (lc *CertsList) Execute([]string) error { 23 | ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) 24 | defer cancel() 25 | 26 | syno := lc.Client() 27 | list, err := syno.ListCerts(ctx) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | return lc.show(list) 33 | } 34 | 35 | //nolint:gomnd 36 | func (lc *CertsList) show(list []client.Certificate) error { 37 | switch lc.Format { 38 | case fmtJSON: 39 | enc := json.NewEncoder(os.Stdout) 40 | enc.SetIndent("", " ") 41 | return enc.Encode(list) 42 | case fmtTable: 43 | fallthrough 44 | default: 45 | tw := tabwriter.NewWriter(os.Stdout, 3, 4, 2, ' ', 0) 46 | _, _ = fmt.Fprintln(tw, 47 | "Status", "\t", 48 | "ID", "\t", 49 | "Name", "\t", 50 | "SAN", "\t", 51 | "Issuer", "\t", 52 | "Since", "\t", 53 | "Expired", "\t", 54 | ) 55 | for _, item := range list { 56 | sign := "" 57 | if item.IsDefault { 58 | sign += "*" 59 | } 60 | if item.IsBroken { 61 | sign += "!" 62 | } 63 | if item.Expired() { 64 | sign += "-" 65 | } 66 | _, _ = fmt.Fprintln(tw, 67 | sign, "\t", 68 | item.ID, "\t", 69 | item.Description, "\t", 70 | strings.Join(item.Subject.SubAltName, ","), "\t", 71 | item.Issuer.CommonName, "\t", 72 | item.ValidFrom.Time().Format(time.RFC822), "\t", 73 | item.ValidTill.Time().Format(time.RFC822), 74 | ) 75 | } 76 | return tw.Flush() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /cmd/syno-cli/commands/certs_upload.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "os" 9 | "os/signal" 10 | "text/tabwriter" 11 | 12 | "github.com/reddec/syno-cli/pkg/client" 13 | ) 14 | 15 | //nolint:staticcheck 16 | type CertsUpload struct { 17 | SynoClient `group:"Synology Client" namespace:"synology" env-namespace:"SYNOLOGY"` 18 | Key string `short:"k" long:"key" env:"KEY" description:"Path to private key. Use - (dash) to read it from stdin" default:"-"` 19 | Cert string `short:"c" long:"cert" env:"CERT" description:"Path to server certificate" required:"true"` 20 | CA string `short:"C" long:"ca" env:"CA" description:"Path to intermediate certificate"` 21 | Format string `short:"f" long:"format" env:"FORMAT" description:"Output format" default:"table" choice:"table" choice:"json"` 22 | Default bool `short:"d" long:"default" env:"DEFAULT" description:"Set certificate as default"` 23 | Args struct { 24 | Name string `positional-arg-name:"name" env:"NAME" description:"certificate name" required:"true"` 25 | } `positional-args:"true"` 26 | } 27 | 28 | func (lc *CertsUpload) Execute([]string) error { 29 | ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) 30 | defer cancel() 31 | 32 | syno := lc.Client() 33 | 34 | var privateFile io.ReadCloser 35 | if lc.Key == "-" { 36 | privateFile = os.Stdin 37 | } else if f, err := os.Open(lc.Key); err == nil { 38 | privateFile = f 39 | } else { 40 | return err 41 | } 42 | defer privateFile.Close() 43 | 44 | certFile, err := os.Open(lc.Cert) 45 | if err != nil { 46 | return err 47 | } 48 | defer certFile.Close() 49 | 50 | var caFile io.Reader 51 | if lc.CA != "" { 52 | f, err := os.Open(lc.CA) 53 | if err != nil { 54 | return err 55 | } 56 | caFile = f 57 | defer f.Close() 58 | } 59 | 60 | info, err := syno.UploadCert(ctx, client.NewCertificate{ 61 | Name: lc.Args.Name, 62 | AsDefault: lc.Default, 63 | Cert: certFile, 64 | CA: caFile, 65 | Key: privateFile, 66 | }) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | return lc.show(info) 72 | } 73 | 74 | //nolint:gomnd 75 | func (lc *CertsUpload) show(info *client.CertUploadResult) error { 76 | switch lc.Format { 77 | case fmtJSON: 78 | enc := json.NewEncoder(os.Stdout) 79 | enc.SetIndent("", " ") 80 | return enc.Encode(info) 81 | case fmtTable: 82 | fallthrough 83 | default: 84 | tw := tabwriter.NewWriter(os.Stdout, 3, 4, 2, ' ', 0) 85 | _, _ = fmt.Fprintln(tw, 86 | "ID", "\t", 87 | "Server restarted", "\t", 88 | ) 89 | _, _ = fmt.Fprintln(tw, 90 | info.CertificateID, "\t", 91 | info.ServerRestarted, "\t", 92 | ) 93 | return tw.Flush() 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /cmd/syno-cli/commands/ds_create.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log/slog" 7 | "net/url" 8 | "os" 9 | "os/signal" 10 | 11 | "github.com/reddec/syno-cli/pkg/client" 12 | ) 13 | 14 | type DsCreate struct { 15 | Logging 16 | SynoClient `group:"Synology Client" namespace:"synology" env-namespace:"SYNOLOGY"` 17 | Format client.FileType `short:"f" long:"format" env:"FORMAT" description:"File format" default:"auto" choice:"torrent" choice:"txt" choice:"nzb" choice:"auto"` 18 | Destination string `short:"d" long:"destination" env:"DESTINATION" description:"Destination directory" default:"Downloads"` 19 | Args struct { 20 | Ref string `positional-arg-name:"ref" description:"URL or file name. If not set or set to - (dash) - STDIN will be used"` 21 | } `positional-args:"yes"` 22 | } 23 | 24 | func (cmd *DsCreate) Execute([]string) error { 25 | cmd.SetupLogging() 26 | 27 | ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) 28 | defer cancel() 29 | 30 | params := client.DownloadTask{ 31 | FileType: cmd.Format, 32 | Destination: cmd.Destination, 33 | } 34 | 35 | syno := cmd.Client() 36 | 37 | if u, err := url.Parse(cmd.Args.Ref); err == nil && u.Scheme != "" { 38 | slog.Debug("ref is URL", "url", u.Redacted()) 39 | params.URL = []string{cmd.Args.Ref} 40 | } else if cmd.Args.Ref == "" || cmd.Args.Ref == "-" { 41 | slog.Debug("ref is STDIN payload") 42 | params.File = os.Stdin 43 | } else { 44 | slog.Debug("ref is file") 45 | f, err := os.Open(cmd.Args.Ref) 46 | if err != nil { 47 | return fmt.Errorf("open file: %w", err) 48 | } 49 | defer f.Close() 50 | params.File = f 51 | } 52 | slog.Debug("creating download task", "destination", params.Destination) 53 | err := syno.DownloadStation().Create(ctx, params) 54 | if err != nil { 55 | return err 56 | } 57 | slog.Info("created task in Download Station") 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /cmd/syno-cli/commands/ds_ls.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | "os/signal" 9 | "text/tabwriter" 10 | "time" 11 | 12 | "github.com/reddec/syno-cli/pkg/client" 13 | ) 14 | 15 | type DsList struct { 16 | Logging 17 | SynoClient `group:"Synology Client" namespace:"synology" env-namespace:"SYNOLOGY"` 18 | Format string `short:"f" long:"format" env:"FORMAT" description:"How to show output" default:"table" choice:"table" choice:"json"` 19 | Offset int `short:"o" long:"offset" env:"OFFSET" description:"Offset" default:"0"` 20 | Limit int `short:"l" long:"limit" env:"LIMIT" description:"Max number of items" default:"1000"` 21 | } 22 | 23 | func (cmd *DsList) Execute([]string) error { 24 | cmd.SetupLogging() 25 | 26 | ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) 27 | defer cancel() 28 | 29 | syno := cmd.Client() 30 | info, err := syno.DownloadStation().List(ctx, cmd.Offset, cmd.Limit) 31 | if err != nil { 32 | return fmt.Errorf("list tasks: %w", err) 33 | } 34 | return cmd.show(info.Tasks) 35 | } 36 | 37 | //nolint:gomnd 38 | func (cmd *DsList) show(list []client.ScheduledTask) error { 39 | switch cmd.Format { 40 | case fmtJSON: 41 | enc := json.NewEncoder(os.Stdout) 42 | enc.SetIndent("", " ") 43 | return enc.Encode(list) 44 | case fmtTable: 45 | fallthrough 46 | default: 47 | tw := tabwriter.NewWriter(os.Stdout, 3, 4, 2, ' ', 0) 48 | _, _ = fmt.Fprintln(tw, 49 | "ID", "\t", 50 | "User", "\t", 51 | "Status", "\t", 52 | "Type", "\t", 53 | "Size", "\t", 54 | "Created", "\t", 55 | "Title", "\t", 56 | ) 57 | for _, item := range list { 58 | _, _ = fmt.Fprintln(tw, 59 | item.ID, "\t", 60 | item.Username, "\t", 61 | item.Status, "\t", 62 | item.Type, "\t", 63 | item.Size, "\t", 64 | time.Unix(item.Additional.Detail.CreateTime, 0).Format(time.RFC3339), "\t", 65 | item.Title, 66 | ) 67 | } 68 | return tw.Flush() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /cmd/syno-cli/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/jessevdk/go-flags" 8 | 9 | "github.com/reddec/syno-cli/cmd/syno-cli/commands" 10 | ) 11 | 12 | //nolint:gochecknoglobals 13 | var ( 14 | version = "dev" 15 | commit = "none" 16 | date = "unknown" 17 | builtBy = "unknown" 18 | ) 19 | 20 | //nolint:staticcheck 21 | type Config struct { 22 | Cert struct { 23 | List commands.CertsList `command:"list" description:"list certificates" alias:"ls" alias:"l"` 24 | Upload commands.CertsUpload `command:"upload" description:"upload certificate" alias:"up" alias:"u"` 25 | Delete commands.CertsDelete `command:"delete" description:"delete certificate" alias:"remove" alias:"rm" alias:"del" alias:"d"` 26 | Auto commands.CertsAuto `command:"auto" description:"automatically issue and push certificates" alias:"dns01" alias:"lego" alias:"a"` 27 | } `command:"cert" description:"manager certificates" alias:"certificates" alias:"certificate" alias:"certs" alias:"cert" alias:"c"` 28 | DS struct { 29 | Create commands.DsCreate `command:"create" description:"create task" alias:"add" alias:"new" alias:"c"` 30 | List commands.DsList `command:"list" description:"list tasks" alias:"ls" alias:"l"` 31 | } `command:"ds" description:"download station" alias:"download-station" alias:"download" alias:"dl" alias:"d"` 32 | } 33 | 34 | func main() { 35 | var config Config 36 | parser := flags.NewParser(&config, flags.Default) 37 | parser.ShortDescription = "Synology CLI" 38 | parser.LongDescription = fmt.Sprintf("Unofficial CLI for Synology DSM\nsyno-cli %s, commit %s, built at %s by %s\nAuthor: Aleksandr Baryshnikov ", version, commit, date, builtBy) 39 | 40 | if _, err := parser.Parse(); err != nil { 41 | os.Exit(1) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/reddec/syno-cli 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/go-acme/lego/v4 v4.5.3 7 | github.com/jessevdk/go-flags v1.6.1 8 | github.com/stretchr/testify v1.9.0 9 | ) 10 | 11 | require ( 12 | cloud.google.com/go v0.54.0 // indirect 13 | github.com/Azure/azure-sdk-for-go v32.4.0+incompatible // indirect 14 | github.com/Azure/go-autorest v14.2.0+incompatible // indirect 15 | github.com/Azure/go-autorest/autorest v0.11.19 // indirect 16 | github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect 17 | github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 // indirect 18 | github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 // indirect 19 | github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect 20 | github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect 21 | github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect 22 | github.com/Azure/go-autorest/logger v0.2.1 // indirect 23 | github.com/Azure/go-autorest/tracing v0.6.0 // indirect 24 | github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect 25 | github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1 // indirect 26 | github.com/aliyun/alibaba-cloud-sdk-go v1.61.1183 // indirect 27 | github.com/aws/aws-sdk-go v1.39.0 // indirect 28 | github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect 29 | github.com/cenkalti/backoff/v4 v4.1.1 // indirect 30 | github.com/cloudflare/cloudflare-go v0.20.0 // indirect 31 | github.com/cpu/goacmedns v0.1.1 // indirect 32 | github.com/davecgh/go-spew v1.1.1 // indirect 33 | github.com/deepmap/oapi-codegen v1.6.1 // indirect 34 | github.com/dimchansky/utfbom v1.1.1 // indirect 35 | github.com/dnsimple/dnsimple-go v0.70.1 // indirect 36 | github.com/exoscale/egoscale v0.67.0 // indirect 37 | github.com/fatih/structs v1.1.0 // indirect 38 | github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect 39 | github.com/go-errors/errors v1.0.1 // indirect 40 | github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 // indirect 41 | github.com/gofrs/uuid v3.2.0+incompatible // indirect 42 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect 43 | github.com/golang/protobuf v1.5.2 // indirect 44 | github.com/google/go-querystring v1.1.0 // indirect 45 | github.com/google/uuid v1.1.1 // indirect 46 | github.com/googleapis/gax-go/v2 v2.0.5 // indirect 47 | github.com/gophercloud/gophercloud v0.16.0 // indirect 48 | github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect 49 | github.com/hashicorp/go-cleanhttp v0.5.1 // indirect 50 | github.com/hashicorp/go-retryablehttp v0.7.0 // indirect 51 | github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect 52 | github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect 53 | github.com/jarcoal/httpmock v1.0.6 // indirect 54 | github.com/jmespath/go-jmespath v0.4.0 // indirect 55 | github.com/json-iterator/go v1.1.7 // indirect 56 | github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect 57 | github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b // indirect 58 | github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect 59 | github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect 60 | github.com/labbsr0x/goh v1.0.1 // indirect 61 | github.com/linode/linodego v0.31.1 // indirect 62 | github.com/liquidweb/go-lwApi v0.0.5 // indirect 63 | github.com/liquidweb/liquidweb-cli v0.6.9 // indirect 64 | github.com/liquidweb/liquidweb-go v1.6.3 // indirect 65 | github.com/mattn/go-isatty v0.0.12 // indirect 66 | github.com/miekg/dns v1.1.43 // indirect 67 | github.com/mitchellh/go-homedir v1.1.0 // indirect 68 | github.com/mitchellh/mapstructure v1.4.1 // indirect 69 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 70 | github.com/modern-go/reflect2 v1.0.1 // indirect 71 | github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect 72 | github.com/nrdcg/auroradns v1.0.1 // indirect 73 | github.com/nrdcg/desec v0.6.0 // indirect 74 | github.com/nrdcg/dnspod-go v0.4.0 // indirect 75 | github.com/nrdcg/freemyip v0.2.0 // indirect 76 | github.com/nrdcg/goinwx v0.8.1 // indirect 77 | github.com/nrdcg/namesilo v0.2.1 // indirect 78 | github.com/nrdcg/porkbun v0.1.1 // indirect 79 | github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect 80 | github.com/ovh/go-ovh v1.1.0 // indirect 81 | github.com/patrickmn/go-cache v2.1.0+incompatible // indirect 82 | github.com/pkg/errors v0.9.1 // indirect 83 | github.com/pmezard/go-difflib v1.0.0 // indirect 84 | github.com/pquerna/otp v1.3.0 // indirect 85 | github.com/sacloud/libsacloud v1.36.2 // indirect 86 | github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f // indirect 87 | github.com/sirupsen/logrus v1.4.2 // indirect 88 | github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect 89 | github.com/softlayer/softlayer-go v1.0.3 // indirect 90 | github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect 91 | github.com/spf13/cast v1.3.1 // indirect 92 | github.com/stretchr/objx v0.5.2 // indirect 93 | github.com/transip/gotransip/v6 v6.6.1 // indirect 94 | github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14 // indirect 95 | github.com/vultr/govultr/v2 v2.7.1 // indirect 96 | go.opencensus.io v0.22.3 // indirect 97 | go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277 // indirect 98 | golang.org/x/crypto v0.28.0 // indirect 99 | golang.org/x/net v0.30.0 // indirect 100 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect 101 | golang.org/x/sys v0.26.0 // indirect 102 | golang.org/x/text v0.19.0 // indirect 103 | golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect 104 | google.golang.org/api v0.20.0 // indirect 105 | google.golang.org/appengine v1.6.5 // indirect 106 | google.golang.org/genproto v0.0.0-20200305110556-506484158171 // indirect 107 | google.golang.org/grpc v1.27.1 // indirect 108 | google.golang.org/protobuf v1.26.0 // indirect 109 | gopkg.in/ini.v1 v1.62.0 // indirect 110 | gopkg.in/ns1/ns1-go.v2 v2.6.2 // indirect 111 | gopkg.in/square/go-jose.v2 v2.6.0 // indirect 112 | gopkg.in/yaml.v2 v2.4.0 // indirect 113 | gopkg.in/yaml.v3 v3.0.1 // indirect 114 | ) 115 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 11 | cloud.google.com/go v0.54.0 h1:3ithwDMr7/3vpAMXiH+ZQnYbuIsh+OPhUPMFC9enmn0= 12 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 13 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 14 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 15 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 16 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 17 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 18 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 19 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 20 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 21 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 22 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 23 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 24 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 25 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 26 | github.com/Azure/azure-sdk-for-go v32.4.0+incompatible h1:1JP8SKfroEakYiQU2ZyPDosh8w2Tg9UopKt88VyQPt4= 27 | github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= 28 | github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= 29 | github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= 30 | github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= 31 | github.com/Azure/go-autorest/autorest v0.11.19 h1:7/IqD2fEYVha1EPeaiytVKhzmPV223pfkRIQUGOK2IE= 32 | github.com/Azure/go-autorest/autorest v0.11.19/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= 33 | github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= 34 | github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk= 35 | github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q= 36 | github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= 37 | github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 h1:TzPg6B6fTZ0G1zBf3T54aI7p3cAT6u//TOXGPmFMOXg= 38 | github.com/Azure/go-autorest/autorest/azure/auth v0.5.8/go.mod h1:kxyKZTSfKh8OVFWPAgOgQ/frrJgeYQJPyR5fLFmXko4= 39 | github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 h1:dMOmEJfkLKW/7JsokJqkyoYSgmR08hi9KrhjZb+JALY= 40 | github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM= 41 | github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= 42 | github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= 43 | github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= 44 | github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= 45 | github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= 46 | github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= 47 | github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac= 48 | github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= 49 | github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= 50 | github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= 51 | github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= 52 | github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= 53 | github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= 54 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 55 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 56 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 57 | github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24= 58 | github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks= 59 | github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1 h1:bLzehmpyCwQiqCE1Qe9Ny6fbFqs7hPlmo9vKv2orUxs= 60 | github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1/go.mod h1:kX6YddBkXqqywAe8c9LyvgTCyFuZCTMF4cRPQhc3Fy8= 61 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 62 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 63 | github.com/aliyun/alibaba-cloud-sdk-go v1.61.1183 h1:dkj8/dxOQ4L1XpwCzRLqukvUBbxuNdz3FeyvHFnRjmo= 64 | github.com/aliyun/alibaba-cloud-sdk-go v1.61.1183/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA= 65 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 66 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 67 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 68 | github.com/aws/aws-sdk-go v1.39.0 h1:74BBwkEmiqBbi2CGflEh34l0YNtIibTjZsibGarkNjo= 69 | github.com/aws/aws-sdk-go v1.39.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= 70 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 71 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 72 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 73 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 74 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= 75 | github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= 76 | github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= 77 | github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw= 78 | github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ= 79 | github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= 80 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 81 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 82 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 83 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 84 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 85 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 86 | github.com/cloudflare/cloudflare-go v0.20.0 h1:y2a6KwYHTFxhw+8PLhz0q5hpTGj6Un3W1pbpQLhzFaE= 87 | github.com/cloudflare/cloudflare-go v0.20.0/go.mod h1:sPWL/lIC6biLEdyGZwBQ1rGQKF1FhM7N60fuNiFdYTI= 88 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 89 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 90 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 91 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 92 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 93 | github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4= 94 | github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ= 95 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 96 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 97 | github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= 98 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 99 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 100 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 101 | github.com/deepmap/oapi-codegen v1.6.1 h1:2BvsmRb6pogGNtr8Ann+esAbSKFXx2CZN18VpAMecnw= 102 | github.com/deepmap/oapi-codegen v1.6.1/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= 103 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 104 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 105 | github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= 106 | github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= 107 | github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= 108 | github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= 109 | github.com/dnsimple/dnsimple-go v0.70.1 h1:cSZndVjttLpgplDuesY4LFIvfKf/zRA1J7mCATBbzSM= 110 | github.com/dnsimple/dnsimple-go v0.70.1/go.mod h1:F9WHww9cC76hrnwGFfAfrqdW99j3MOYasQcIwTS/aUk= 111 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 112 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 113 | github.com/exoscale/egoscale v0.67.0 h1:qgWh7T5IZGrNWtg6ib4dr+76WThvB+odTtGG+DGbXF8= 114 | github.com/exoscale/egoscale v0.67.0/go.mod h1:wi0myUxPsV8SdEtdJHQJxFLL/wEw9fiw9Gs1PWRkvkM= 115 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 116 | github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= 117 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 118 | github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= 119 | github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= 120 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 121 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 122 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 123 | github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= 124 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 125 | github.com/go-acme/lego/v4 v4.5.3 h1:v5RSN8l+RAeNHKTSL80eqLiec6q6UNaFpl2Df5x/5tM= 126 | github.com/go-acme/lego/v4 v4.5.3/go.mod h1:mL1DY809LzjvRuaxINNxsI26f5oStVhBGTpJMiinkZM= 127 | github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= 128 | github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s= 129 | github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= 130 | github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= 131 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 132 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 133 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 134 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 135 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 136 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 137 | github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 138 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 139 | github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 h1:JVrqSeQfdhYRFk24TvhTZWU0q8lfCojxZQFi3Ou7+uY= 140 | github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8= 141 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 142 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 143 | github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA= 144 | github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw= 145 | github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= 146 | github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 147 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 148 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 149 | github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= 150 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 151 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 152 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 153 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 154 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 155 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= 156 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 157 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 158 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 159 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 160 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 161 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 162 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 163 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 164 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 165 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 166 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 167 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 168 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 169 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 170 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 171 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 172 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 173 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 174 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 175 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 176 | github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= 177 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 178 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 179 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 180 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 181 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 182 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 183 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 184 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 185 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 186 | github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= 187 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 188 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 189 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 190 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 191 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 192 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 193 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 194 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 195 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 196 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 197 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 198 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 199 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 200 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 201 | github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= 202 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 203 | github.com/gophercloud/gophercloud v0.15.1-0.20210202035223-633d73521055/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4= 204 | github.com/gophercloud/gophercloud v0.16.0 h1:sWjPfypuzxRxjVbk3/MsU4H8jS0NNlyauZtIUl78BPU= 205 | github.com/gophercloud/gophercloud v0.16.0/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4= 206 | github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae h1:Hi3IgB9RQDE15Kfovd8MTZrcana+UlQqNbOif8dLpA0= 207 | github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae/go.mod h1:wx8HMD8oQD0Ryhz6+6ykq75PJ79iPyEqYHfwZ4l7OsA= 208 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 209 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 210 | github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 211 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 212 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 213 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 214 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 215 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 216 | github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= 217 | github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= 218 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 219 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 220 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 221 | github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= 222 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 223 | github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= 224 | github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= 225 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 226 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 227 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 228 | github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4= 229 | github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= 230 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 231 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 232 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 233 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 234 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 235 | github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 236 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 237 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 238 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 239 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 240 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 241 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 242 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 243 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 244 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 245 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 246 | github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df h1:MZf03xP9WdakyXhOWuAD5uPK3wHh96wCsqe3hCMKh8E= 247 | github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4= 248 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 249 | github.com/infobloxopen/infoblox-go-client v1.1.1 h1:728A6LbLjptj/7kZjHyIxQnm768PWHfGFm0HH8FnbtU= 250 | github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI= 251 | github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= 252 | github.com/jarcoal/httpmock v1.0.6 h1:e81vOSexXU3mJuJ4l//geOmKIt+Vkxerk1feQBC8D0g= 253 | github.com/jarcoal/httpmock v1.0.6/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= 254 | github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4= 255 | github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc= 256 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 257 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 258 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 259 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 260 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 261 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 262 | github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 263 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 264 | github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= 265 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 266 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 267 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 268 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 269 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 270 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 271 | github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg= 272 | github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= 273 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 274 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 275 | github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b h1:DzHy0GlWeF0KAglaTMY7Q+khIFoG8toHP+wLFBVBQJc= 276 | github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ= 277 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 278 | github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= 279 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 280 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 281 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 282 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 283 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 284 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 285 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 286 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 287 | github.com/labbsr0x/bindman-dns-webhook v1.0.2 h1:I7ITbmQPAVwrDdhd6dHKi+MYJTJqPCK0jE6YNBAevnk= 288 | github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA= 289 | github.com/labbsr0x/goh v1.0.1 h1:97aBJkDjpyBZGPbQuOK5/gHcSFbcr5aRsq3RSRJFpPk= 290 | github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w= 291 | github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= 292 | github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= 293 | github.com/linode/linodego v0.31.1 h1:dBtjKo7J9UhNFhTOclEXb12RRyQDaRBxISdONVuU+DA= 294 | github.com/linode/linodego v0.31.1/go.mod h1:BR0gVkCJffEdIGJSl6bHR80Ty+Uvg/2jkjmrWaFectM= 295 | github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= 296 | github.com/liquidweb/go-lwApi v0.0.5 h1:CT4cdXzJXmo0bon298kS7NeSk+Gt8/UHpWBBol1NGCA= 297 | github.com/liquidweb/go-lwApi v0.0.5/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= 298 | github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAWPjVnwoYM= 299 | github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ= 300 | github.com/liquidweb/liquidweb-go v1.6.3 h1:NVHvcnX3eb3BltiIoA+gLYn15nOpkYkdizOEYGSKrk4= 301 | github.com/liquidweb/liquidweb-go v1.6.3/go.mod h1:SuXXp+thr28LnjEw18AYtWwIbWMHSUiajPQs8T9c/Rc= 302 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 303 | github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= 304 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 305 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 306 | github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= 307 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 308 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 309 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 310 | github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 311 | github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 312 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 313 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 314 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= 315 | github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= 316 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 317 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 318 | github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 319 | github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 320 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 321 | github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= 322 | github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= 323 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 324 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 325 | github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= 326 | github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= 327 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 328 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 329 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 330 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 331 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 332 | github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0= 333 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 334 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 335 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 336 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 337 | github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 338 | github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= 339 | github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 340 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 341 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 342 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 343 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 344 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 345 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 346 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 347 | github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g= 348 | github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8= 349 | github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= 350 | github.com/nrdcg/auroradns v1.0.1 h1:m/kBq83Xvy3cU261MOknd8BdnOk12q4lAWM+kOdsC2Y= 351 | github.com/nrdcg/auroradns v1.0.1/go.mod h1:y4pc0i9QXYlFCWrhWrUSIETnZgrf4KuwjDIWmmXo3JI= 352 | github.com/nrdcg/desec v0.6.0 h1:kZ9JtsYEW3LNfuPIM+2tXoxoQlF9koWfQTWTQsA7Sr8= 353 | github.com/nrdcg/desec v0.6.0/go.mod h1:wybWg5cRrNmtXLYpUCPCLvz4jfFNEGZQEnoUiX9WqcY= 354 | github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U= 355 | github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ= 356 | github.com/nrdcg/freemyip v0.2.0 h1:/GscavT4GVqAY13HExl5UyoB4wlchv6Cg5NYDGsUoJ8= 357 | github.com/nrdcg/freemyip v0.2.0/go.mod h1:HjF0Yz0lSb37HD2ihIyGz9esyGcxbCrrGFLPpKevbx4= 358 | github.com/nrdcg/goinwx v0.8.1 h1:20EQ/JaGFnSKwiDH2JzjIpicffl3cPk6imJBDqVBVtU= 359 | github.com/nrdcg/goinwx v0.8.1/go.mod h1:tILVc10gieBp/5PMvbcYeXM6pVQ+c9jxDZnpaR1UW7c= 360 | github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg= 361 | github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw= 362 | github.com/nrdcg/porkbun v0.1.1 h1:gxVzQYfFUGXhnBax/aVugoE3OIBAdHgrJgyMPyY5Sjo= 363 | github.com/nrdcg/porkbun v0.1.1/go.mod h1:JWl/WKnguWos4mjfp4YizvvToigk9qpQwrodOk+CPoA= 364 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 365 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 366 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 367 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 368 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 369 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 370 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 371 | github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= 372 | github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= 373 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 374 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 375 | github.com/onsi/gomega v1.14.0 h1:ep6kpPVwmr/nTbklSx2nrLNSIO62DoYAhnPNIMhK8gI= 376 | github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= 377 | github.com/oracle/oci-go-sdk v24.3.0+incompatible h1:x4mcfb4agelf1O4/1/auGlZ1lr97jXRSSN5MxTgG/zU= 378 | github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= 379 | github.com/ovh/go-ovh v1.1.0 h1:bHXZmw8nTgZin4Nv7JuaLs0KG5x54EQR7migYTd1zrk= 380 | github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA= 381 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 382 | github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= 383 | github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= 384 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 385 | github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= 386 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 387 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 388 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 389 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 390 | github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= 391 | github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= 392 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 393 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 394 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 395 | github.com/pquerna/otp v1.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs= 396 | github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= 397 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 398 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 399 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 400 | github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= 401 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 402 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 403 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 404 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 405 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 406 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 407 | github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= 408 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 409 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 410 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 411 | github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= 412 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 413 | github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA= 414 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 415 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 416 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 417 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 418 | github.com/sacloud/libsacloud v1.36.2 h1:aosI7clbQ9IU0Hj+3rpk3SKJop5nLPpLThnWCivPqjI= 419 | github.com/sacloud/libsacloud v1.36.2/go.mod h1:P7YAOVmnIn3DKHqCZcUKYUXmSwGBm3yS7IBEjKVSrjg= 420 | github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f h1:WSnaD0/cvbKJgSTYbjAPf4RJXVvNNDAwVm+W8wEmnGE= 421 | github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= 422 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 423 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 424 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 425 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= 426 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 427 | github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= 428 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 429 | github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= 430 | github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= 431 | github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q= 432 | github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= 433 | github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 434 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= 435 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 436 | github.com/smartystreets/gunit v1.0.4 h1:tpTjnuH7MLlqhoD21vRoMZbMIi5GmBsAJDFyF67GhZA= 437 | github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ= 438 | github.com/softlayer/softlayer-go v1.0.3 h1:9FONm5xzQ9belQtbdryR6gBg4EF6hX6lrjNKi0IvZkU= 439 | github.com/softlayer/softlayer-go v1.0.3/go.mod h1:6HepcfAXROz0Rf63krk5hPZyHT6qyx2MNvYyHof7ik4= 440 | github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e h1:3OgWYFw7jxCZPcvAg+4R8A50GZ+CCkARF10lxu2qDsQ= 441 | github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e/go.mod h1:fKZCUVdirrxrBpwd9wb+lSoVixvpwAu8eHzbQB2tums= 442 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 443 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 444 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 445 | github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= 446 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 447 | github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= 448 | github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 449 | github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= 450 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 451 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 452 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 453 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 454 | github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= 455 | github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= 456 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 457 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 458 | github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 459 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 460 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 461 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 462 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 463 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 464 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 465 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 466 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 467 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 468 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 469 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 470 | github.com/transip/gotransip/v6 v6.6.1 h1:nsCU1ErZS5G0FeOpgGXc4FsWvBff9GPswSMggsC4564= 471 | github.com/transip/gotransip/v6 v6.6.1/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g= 472 | github.com/uber-go/atomic v1.3.2 h1:Azu9lPBWRNKzYXSIwRfgRuDuS0YKsK4NFhiQv98gkxo= 473 | github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= 474 | github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 475 | github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= 476 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 477 | github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= 478 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 479 | github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14 h1:TFXGGMHmml4rs29PdPisC/aaCzOxUu1Vsh9on/IpUfE= 480 | github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14/go.mod h1:RWc47jtnVuQv6+lY3c768WtXCas/Xi+U5UFc5xULmYg= 481 | github.com/vultr/govultr/v2 v2.7.1 h1:uF9ERet++Gb+7Cqs3p1P6b6yebeaZqVd7t5P2uZCaJU= 482 | github.com/vultr/govultr/v2 v2.7.1/go.mod h1:BvOhVe6/ZpjwcoL6/unkdQshmbS9VGbowI4QT+3DGVU= 483 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 484 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= 485 | github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= 486 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 487 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 488 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 489 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 490 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 491 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 492 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 493 | go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= 494 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 495 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 496 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 497 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 498 | go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277 h1:d9qaMM+ODpCq+9We41//fu/sHsTnXcrqd1en3x+GKy4= 499 | go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= 500 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 501 | golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 502 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 503 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 504 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 505 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 506 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 507 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 508 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 509 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 510 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 511 | golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 512 | golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 513 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 514 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= 515 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 516 | golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= 517 | golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 518 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 519 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 520 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 521 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 522 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 523 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 524 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 525 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 526 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 527 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 528 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 529 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 530 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 531 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 532 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 533 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 534 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 535 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 536 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 537 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 538 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 539 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 540 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 541 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 542 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 543 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 544 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 545 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 546 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 547 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 548 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 549 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 550 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 551 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 552 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 553 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 554 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 555 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 556 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 557 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 558 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 559 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 560 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 561 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 562 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 563 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 564 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 565 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 566 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 567 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 568 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 569 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 570 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 571 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 572 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 573 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 574 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 575 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 576 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 577 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 578 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= 579 | golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 580 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 581 | golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= 582 | golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= 583 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 584 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 585 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 586 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 587 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= 588 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 589 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 590 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 591 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 592 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 593 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 594 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 595 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 596 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 597 | golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= 598 | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 599 | golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 600 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 601 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 602 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 603 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 604 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 605 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 606 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 607 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 608 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 609 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 610 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 611 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 612 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 613 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 614 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 615 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 616 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 617 | golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 618 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 619 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 620 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 621 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 622 | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 623 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 624 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 625 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 626 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 627 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 628 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 629 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 630 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 631 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 632 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 633 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 634 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 635 | golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 636 | golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 637 | golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 638 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 639 | golang.org/x/sys v0.0.0-20201110211018-35f3e6cf4a65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 640 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 641 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 642 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 643 | golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 644 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 645 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 646 | golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= 647 | golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 648 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 649 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 650 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 651 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 652 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 653 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 654 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 655 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 656 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 657 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 658 | golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= 659 | golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 660 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 661 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 662 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 663 | golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 664 | golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 665 | golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 h1:Vv0JUPWTyeqUq42B2WJ1FeIDjjvGKoA2Ss+Ts0lAVbs= 666 | golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 667 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 668 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 669 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 670 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 671 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 672 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 673 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 674 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 675 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 676 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 677 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 678 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 679 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 680 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 681 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 682 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 683 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 684 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 685 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 686 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 687 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 688 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 689 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 690 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 691 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 692 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 693 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 694 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 695 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 696 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 697 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 698 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 699 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 700 | golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 701 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 702 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 703 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 704 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 705 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 706 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 707 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 708 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 709 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 710 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 711 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 712 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 713 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 714 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 715 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 716 | google.golang.org/api v0.20.0 h1:jz2KixHX7EcCPiQrySzPdnYT7DbINAypCqKZ1Z7GM40= 717 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 718 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 719 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 720 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 721 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 722 | google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= 723 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 724 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 725 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 726 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 727 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 728 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 729 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 730 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 731 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 732 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 733 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 734 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 735 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 736 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 737 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 738 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 739 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 740 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 741 | google.golang.org/genproto v0.0.0-20200305110556-506484158171 h1:xes2Q2k+d/+YNXVw0FpZkIDJiaux4OVrRKXRAzH6A0U= 742 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 743 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 744 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 745 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 746 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 747 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 748 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 749 | google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk= 750 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 751 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 752 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 753 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 754 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 755 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 756 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 757 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 758 | google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= 759 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 760 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 761 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 762 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 763 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 764 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 765 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 766 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 767 | gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0= 768 | gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= 769 | gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 770 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 771 | gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 772 | gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 773 | gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= 774 | gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 775 | gopkg.in/ns1/ns1-go.v2 v2.6.2 h1:tC+gRSN6fmnb9l9cVrIysXyuRO0wV6cZrjDqlMB0gGc= 776 | gopkg.in/ns1/ns1-go.v2 v2.6.2/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYhsPk= 777 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 778 | gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= 779 | gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 780 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 781 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 782 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 783 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 784 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 785 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 786 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 787 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 788 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 789 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 790 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 791 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 792 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 793 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 794 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 795 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 796 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 797 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 798 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 799 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 800 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 801 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 802 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 803 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 804 | -------------------------------------------------------------------------------- /pkg/client/base.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "log/slog" 11 | "mime/multipart" 12 | "net/http" 13 | "net/http/cookiejar" 14 | "net/textproto" 15 | "net/url" 16 | "os" 17 | "strconv" 18 | "strings" 19 | "sync" 20 | "sync/atomic" 21 | "time" 22 | ) 23 | 24 | const ( 25 | EnvURL = "SYNOLOGY_URL" 26 | EnvUser = "SYNOLOGY_USER" 27 | EnvPass = "SYNOLOGY_PASSWORD" //nolint:gosec 28 | ) 29 | 30 | const DefaultTimeout = 30 * time.Second 31 | 32 | var ErrBadStatus = errors.New("bad response status") 33 | 34 | type HTTPClient interface { 35 | Do(req *http.Request) (*http.Response, error) 36 | } 37 | 38 | type HTTPClientFunc func(req *http.Request) (*http.Response, error) 39 | 40 | func (hf HTTPClientFunc) Do(req *http.Request) (*http.Response, error) { 41 | return hf(req) 42 | } 43 | 44 | type Config struct { 45 | Client HTTPClient // HTTP client to perform requests, default is new HTTP client. Client MUST support cookies. Keep it nil for most cases is a good idea. 46 | User string // User name 47 | Password string // User password 48 | URL string // Synology url, default is http://localhost:5000 49 | } 50 | 51 | // Default client based on env variables. 52 | func Default() *Client { 53 | return New(FromEnv(nil)) 54 | } 55 | 56 | // FromEnv creates config based on standard environment variables. If envFunc not defined, 57 | // os.Getenv will be used. 58 | func FromEnv(envFunc func(string) string) Config { 59 | if envFunc == nil { 60 | envFunc = os.Getenv 61 | } 62 | 63 | return Config{ 64 | User: envFunc(EnvUser), 65 | Password: envFunc(EnvPass), 66 | URL: envFunc(EnvURL), 67 | } 68 | } 69 | 70 | // New instance of Synology API client. 71 | func New(cfg Config) *Client { 72 | if cfg.Client == nil { 73 | jar, err := cookiejar.New(nil) 74 | if err != nil { 75 | panic(err) // CAN NOT happen at go 1.23 76 | } 77 | cfg.Client = &http.Client{ 78 | Jar: jar, 79 | Timeout: DefaultTimeout, 80 | } 81 | } 82 | if cfg.URL == "" { 83 | cfg.URL = "http://localhost:5000" 84 | } else { 85 | cfg.URL = strings.TrimRight(cfg.URL, "/") 86 | } 87 | 88 | return &Client{ 89 | client: cfg.Client, 90 | user: cfg.User, 91 | password: cfg.Password, 92 | baseURL: cfg.URL, 93 | } 94 | } 95 | 96 | type Client struct { 97 | client HTTPClient 98 | user string 99 | password string 100 | baseURL string 101 | authorized atomic.Bool 102 | authLock sync.Mutex 103 | versionLock sync.Mutex 104 | versions map[string]API 105 | } 106 | 107 | // WithClient returns copy of Synology client with custom HTTP client. 108 | func (cl *Client) WithClient(client HTTPClient) *Client { 109 | cl.versionLock.Lock() 110 | defer cl.versionLock.Unlock() 111 | cl.authLock.Lock() 112 | defer cl.authLock.Unlock() 113 | 114 | return &Client{ 115 | client: client, 116 | user: cl.user, 117 | password: cl.password, 118 | baseURL: cl.baseURL, 119 | versions: cl.versions, 120 | } 121 | } 122 | 123 | // APIVersion returns max version for specific API. It queries Synology for all APIs and caches result. 124 | func (cl *Client) APIVersion(ctx context.Context, apiName string) (API, error) { 125 | if m := cl.versions; m != nil { 126 | return m[apiName], nil 127 | } 128 | cl.versionLock.Lock() 129 | defer cl.versionLock.Unlock() 130 | if m := cl.versions; m != nil { 131 | return m[apiName], nil 132 | } 133 | 134 | err := cl.doPost(ctx, "/webapi/query.cgi", nil, map[string]interface{}{ 135 | "method": "query", 136 | "api": "SYNO.API.Info", 137 | "version": 1, 138 | }, &cl.versions) 139 | if err != nil { 140 | return API{}, fmt.Errorf("invoke api: %w", err) 141 | } 142 | 143 | return cl.versions[apiName], nil 144 | } 145 | 146 | // Login to Synology and get token. Token will be cached. If token already obtained, API call will not be executed. 147 | func (cl *Client) Login(ctx context.Context) error { 148 | if cl.authorized.Load() { 149 | return nil 150 | } 151 | 152 | cl.authLock.Lock() 153 | defer cl.authLock.Unlock() 154 | if cl.authorized.Load() { 155 | return nil 156 | } 157 | 158 | res, err := cl.directCall(ctx, "SYNO.API.Auth", "login", []field{ 159 | {Name: "enable_syno_token", Value: "no"}, 160 | {Name: "account", Value: cl.user}, 161 | {Name: "passwd", Value: cl.password}, 162 | {Name: "format", Value: "cookie"}, 163 | }) 164 | if err != nil { 165 | return fmt.Errorf("invoke api: %w", err) 166 | } 167 | 168 | defer res.Body.Close() 169 | _, _ = io.Copy(io.Discard, res.Body) 170 | cl.authorized.Store(true) 171 | return nil 172 | } 173 | 174 | // DownloadStation API 175 | func (cl *Client) DownloadStation() *DownloadStation { 176 | return &DownloadStation{cl: cl} 177 | } 178 | 179 | func (cl *Client) callAPI(ctx context.Context, apiName, method string, params map[string]interface{}, out interface{}) error { 180 | info, err := cl.APIVersion(ctx, apiName) 181 | if err != nil { 182 | return fmt.Errorf("get API %s version: %w", apiName, err) 183 | } 184 | 185 | var queryParams = map[string]interface{}{ 186 | "method": method, 187 | "api": apiName, 188 | "version": info.MaxVersion, 189 | } 190 | 191 | // if it's not upload, we can merge transport params into payload 192 | if !needStreaming(params) { 193 | if params == nil { 194 | params = queryParams 195 | } else { 196 | for k, v := range queryParams { 197 | params[k] = v 198 | } 199 | queryParams = nil 200 | } 201 | } 202 | 203 | return cl.doPost(ctx, "/webapi/"+info.Path, queryParams, params, out) 204 | } 205 | 206 | // deprecated, use directCall instead 207 | func (cl *Client) doPost(ctx context.Context, path string, queryParams map[string]interface{}, params map[string]interface{}, out interface{}) error { 208 | var contentType string 209 | var content io.ReadCloser 210 | if needStreaming(params) { 211 | contentType, content = streamData(mapToFields(params)) 212 | } else { 213 | contentType, content = plainData(mapToFields(params)) 214 | } 215 | defer content.Close() 216 | 217 | req, err := http.NewRequestWithContext(ctx, http.MethodPost, cl.baseURL+path+"?"+joinParams(mapToFields(queryParams)), content) 218 | if err != nil { 219 | return fmt.Errorf("prepare request: %w", err) 220 | } 221 | req.Header.Set("Content-Type", contentType) 222 | 223 | res, err := cl.client.Do(req) 224 | if err != nil { 225 | return fmt.Errorf("execute request: %w", err) 226 | } 227 | defer res.Body.Close() 228 | 229 | if res.StatusCode != http.StatusOK { 230 | return fmt.Errorf("status %d: %w", res.StatusCode, ErrBadStatus) 231 | } 232 | 233 | var rawResponse apiResponse 234 | 235 | err = json.NewDecoder(res.Body).Decode(&rawResponse) 236 | if err != nil { 237 | return fmt.Errorf("decode response: %w", err) 238 | } 239 | 240 | if err := rawResponse.Error; err != nil { 241 | return fmt.Errorf("response: %w", err) 242 | } 243 | err = json.Unmarshal(rawResponse.RawData, out) 244 | if err != nil { 245 | return fmt.Errorf("decode payload: %w", err) 246 | } 247 | 248 | return nil 249 | } 250 | 251 | func (cl *Client) directCall(ctx context.Context, apiName string, method string, params []field) (*http.Response, error) { 252 | info, err := cl.APIVersion(ctx, apiName) 253 | if err != nil { 254 | return nil, fmt.Errorf("get API %s version: %w", apiName, err) 255 | } 256 | 257 | params = append([]field{ 258 | {Name: "api", Value: apiName}, 259 | {Name: "version", Value: info.MaxVersion}, 260 | {Name: "method", Value: method}, 261 | }, params...) 262 | requestURL := cl.baseURL + "/webapi/" + info.Path + "/" + apiName 263 | 264 | var contentType string 265 | var content io.ReadCloser 266 | if needStreamingIter(params) { 267 | contentType, content = streamData(params) 268 | } else { 269 | contentType, content = plainData(params) 270 | } 271 | defer content.Close() 272 | 273 | slog.Debug("API request prepared", "url", requestURL) 274 | req, err := http.NewRequestWithContext(ctx, http.MethodPost, requestURL, content) 275 | if err != nil { 276 | return nil, fmt.Errorf("create request: %w", err) 277 | } 278 | req.Header.Set("Content-Type", contentType) 279 | 280 | res, err := cl.client.Do(req) 281 | if err != nil { 282 | return nil, fmt.Errorf("call API: %w", err) 283 | } 284 | defer res.Body.Close() 285 | 286 | //nolint:mnd 287 | if res.StatusCode/100 != 2 { 288 | _ = res.Body.Close() 289 | return nil, fmt.Errorf("status %d: %w", res.StatusCode, ErrBadStatus) 290 | } 291 | 292 | // try to parse body as API response 293 | var buffer bytes.Buffer 294 | if err := asAPIError(io.TeeReader(res.Body, &buffer)); err != nil { 295 | _ = res.Body.Close() 296 | return nil, fmt.Errorf("application API error: %w", err) 297 | } 298 | res.Body = &readCloser{ 299 | Reader: io.MultiReader(&buffer, res.Body), 300 | Closer: res.Body, 301 | } 302 | 303 | return res, nil 304 | } 305 | 306 | func asAPIError(data io.Reader) error { 307 | var rawResponse apiResponse 308 | 309 | err := json.NewDecoder(data).Decode(&rawResponse) 310 | if err != nil { 311 | return fmt.Errorf("decode response: %w", err) 312 | } 313 | if !rawResponse.Success { 314 | return rawResponse.Error 315 | } 316 | return nil 317 | } 318 | 319 | func plainData(params []field) (string, io.ReadCloser) { 320 | return "application/x-www-form-urlencoded", io.NopCloser(strings.NewReader(joinParams(params))) 321 | } 322 | 323 | func streamData(fields []field) (string, io.ReadCloser) { 324 | reader, writer := io.Pipe() 325 | mp := multipart.NewWriter(writer) 326 | 327 | go func() { 328 | err := streamMultipart(fields, mp) 329 | if err == nil { 330 | err = mp.Close() 331 | } 332 | _ = writer.CloseWithError(err) 333 | }() 334 | return mp.FormDataContentType(), reader 335 | } 336 | 337 | func joinParams(params []field) string { 338 | var buffer bytes.Buffer 339 | for _, field := range params { 340 | if buffer.Len() > 0 { 341 | buffer.WriteRune('&') 342 | } 343 | buffer.WriteString(url.QueryEscape(field.Name)) 344 | buffer.WriteRune('=') 345 | if v, ok := field.Value.([]byte); ok { 346 | buffer.WriteString(url.QueryEscape(string(v))) 347 | } else { 348 | buffer.WriteString(url.QueryEscape(fmt.Sprint(field.Value))) 349 | } 350 | } 351 | return buffer.String() 352 | } 353 | 354 | type field struct { 355 | Name string 356 | Value interface{} // Reader, fileAttachment, []byte, string, else (Sprint'able) 357 | } 358 | 359 | func streamMultipart(fields []field, w *multipart.Writer) error { 360 | for _, field := range fields { 361 | var dest io.Writer 362 | var source io.Reader 363 | switch v := field.Value.(type) { 364 | case io.Reader: 365 | out, err := w.CreateFormField(field.Name) 366 | if err != nil { 367 | return fmt.Errorf("create part for %s: %w", field.Name, err) 368 | } 369 | dest = out 370 | source = v 371 | case fileAttachment: 372 | out, err := w.CreateFormFile(field.Name, v.FileName) 373 | if err != nil { 374 | return fmt.Errorf("create part for %s: %w", field.Name, err) 375 | } 376 | dest = out 377 | source = v.Reader 378 | case []byte: 379 | out, err := w.CreateFormField(field.Name) 380 | if err != nil { 381 | return fmt.Errorf("create part for %s: %w", field.Name, err) 382 | } 383 | dest = out 384 | source = bytes.NewReader(v) 385 | case string: 386 | h := make(textproto.MIMEHeader) 387 | h.Set("Content-Disposition", `form-data; name=`+strconv.Quote(field.Name)) 388 | 389 | out, err := w.CreatePart(h) 390 | if err != nil { 391 | return fmt.Errorf("create part for %s: %w", field.Name, err) 392 | } 393 | dest = out 394 | source = strings.NewReader(v) 395 | default: 396 | h := make(textproto.MIMEHeader) 397 | h.Set("Content-Disposition", `form-data; name=`+strconv.Quote(field.Name)) 398 | out, err := w.CreatePart(h) 399 | if err != nil { 400 | return fmt.Errorf("create part for %s: %w", field.Name, err) 401 | } 402 | dest = out 403 | source = strings.NewReader(fmt.Sprint(v)) 404 | } 405 | if _, err := io.Copy(dest, source); err != nil { 406 | return fmt.Errorf("copy content for part %s: %w", field.Name, err) 407 | } 408 | } 409 | return nil 410 | } 411 | 412 | // deprecated 413 | func needStreaming(params map[string]interface{}) bool { 414 | for _, v := range params { 415 | switch v.(type) { 416 | case io.Reader, *fileAttachment, fileAttachment: 417 | return true 418 | } 419 | } 420 | return false 421 | } 422 | 423 | func needStreamingIter(params []field) bool { 424 | for _, f := range params { 425 | switch f.Value.(type) { 426 | case io.Reader, *fileAttachment, fileAttachment: 427 | return true 428 | } 429 | } 430 | return false 431 | } 432 | 433 | type fileAttachment struct { 434 | FileName string 435 | Reader io.Reader 436 | } 437 | 438 | type apiResponse struct { 439 | Success bool `json:"success"` 440 | Error *RemoteError `json:"error,omitempty"` 441 | RawData json.RawMessage `json:"data"` 442 | } 443 | 444 | type API struct { 445 | MaxVersion int64 `json:"maxVersion"` 446 | Path string `json:"path"` 447 | } 448 | 449 | type RemoteError struct { 450 | Code int64 `json:"code"` 451 | } 452 | 453 | func (e *RemoteError) Error() string { 454 | return "API error code: " + strconv.FormatInt(e.Code, 10) 455 | } 456 | 457 | type readCloser struct { 458 | io.Reader 459 | io.Closer 460 | } 461 | 462 | // deprecated, used for compatibility only 463 | func mapToFields(fields map[string]interface{}) []field { 464 | l := make([]field, 0, len(fields)) 465 | for k, v := range fields { 466 | l = append(l, field{ 467 | Name: k, 468 | Value: v, 469 | }) 470 | } 471 | return l 472 | } 473 | -------------------------------------------------------------------------------- /pkg/client/base_test.go: -------------------------------------------------------------------------------- 1 | package client_test 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "log/slog" 7 | "os" 8 | "strings" 9 | "testing" 10 | "time" 11 | 12 | "github.com/reddec/syno-cli/pkg/client" 13 | 14 | "github.com/stretchr/testify/assert" 15 | "github.com/stretchr/testify/require" 16 | ) 17 | 18 | func TestClient_APIVersion(t *testing.T) { 19 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 20 | defer cancel() 21 | 22 | syno := client.New(client.FromEnv(environ())) 23 | info, err := syno.APIVersion(ctx, "SYNO.API.Info") 24 | require.NoError(t, err) 25 | assert.NotEmpty(t, info.Path) 26 | assert.NotEmpty(t, info.MaxVersion) 27 | } 28 | 29 | func TestClient_Login(t *testing.T) { 30 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 31 | defer cancel() 32 | 33 | syno := client.New(client.FromEnv(environ())) 34 | err := syno.Login(ctx) 35 | require.NoError(t, err) 36 | } 37 | 38 | func environ() func(string) string { 39 | var env = make(map[string]string) 40 | for _, kv := range os.Environ() { 41 | parts := strings.SplitN(kv, "=", 2) 42 | env[parts[0]] = parts[1] 43 | } 44 | 45 | f, err := os.Open("../../.env") 46 | if err != nil { 47 | slog.Error("env not loaded", "error", err) 48 | return os.Getenv 49 | } 50 | defer f.Close() 51 | 52 | scanner := bufio.NewScanner(f) 53 | for scanner.Scan() { 54 | line := scanner.Text() 55 | if strings.HasPrefix(line, "#") { 56 | continue 57 | } 58 | kv := strings.SplitN(line, "=", 2) 59 | env[kv[0]] = kv[1] 60 | } 61 | 62 | return func(s string) string { 63 | return env[s] 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /pkg/client/certs.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "time" 9 | ) 10 | 11 | type Issuer struct { 12 | CommonName string `json:"common_name"` 13 | Country string `json:"country"` 14 | Organization string `json:"organization"` 15 | } 16 | 17 | type Service struct { 18 | DisplayName string `json:"display_name"` 19 | DisplayNameI18N string `json:"display_name_i18n,omitempty"` 20 | IsPkg bool `json:"isPkg"` 21 | Owner string `json:"owner"` 22 | Service string `json:"service"` 23 | Subscriber string `json:"subscriber"` 24 | MultipleCert bool `json:"multiple_cert,omitempty"` 25 | UserSetable bool `json:"user_setable,omitempty"` 26 | } 27 | 28 | type Subject struct { 29 | CommonName string `json:"common_name"` 30 | SubAltName []string `json:"sub_alt_name"` 31 | } 32 | 33 | type Certificate struct { 34 | ID string `json:"id"` 35 | Description string `json:"desc"` 36 | IsBroken bool `json:"is_broken"` 37 | IsDefault bool `json:"is_default"` 38 | Issuer Issuer `json:"issuer"` 39 | KeyTypes string `json:"key_types"` 40 | Renewable bool `json:"renewable"` 41 | Services []Service `json:"services"` 42 | SignatureAlgorithm string `json:"signature_algorithm"` 43 | Subject Subject `json:"subject"` 44 | UserDeletable bool `json:"user_deletable"` 45 | ValidFrom CTime `json:"valid_from"` 46 | ValidTill CTime `json:"valid_till"` 47 | } 48 | 49 | func (ct *Certificate) Expired() bool { 50 | return time.Now().After(ct.ValidTill.Time()) 51 | } 52 | 53 | func (cl *Client) ListCerts(ctx context.Context) ([]Certificate, error) { 54 | if err := cl.Login(ctx); err != nil { 55 | return nil, fmt.Errorf("login: %w", err) 56 | } 57 | var response struct { 58 | Certificates []Certificate `json:"certificates"` 59 | } 60 | 61 | if err := cl.callAPI(ctx, "SYNO.Core.Certificate.CRT", "list", nil, &response); err != nil { 62 | return nil, fmt.Errorf("call api: %w", err) 63 | } 64 | 65 | return response.Certificates, nil 66 | } 67 | 68 | type NewCertificate struct { 69 | Name string // unique logical name for certificate 70 | AsDefault bool // use certificate as default 71 | Cert io.Reader // PEM certificate 72 | CA io.Reader // optional 73 | Key io.Reader // PEM private key 74 | } 75 | 76 | // UploadCert uploads certificate to Synology. Replaces if name (used field description) already exists. 77 | func (cl *Client) UploadCert(ctx context.Context, draft NewCertificate) (*CertUploadResult, error) { 78 | var info CertUploadResult 79 | list, err := cl.ListCerts(ctx) 80 | if err != nil { 81 | return nil, fmt.Errorf("list certificates: %w", err) 82 | } 83 | // find if already exists 84 | var id string 85 | for _, crt := range list { 86 | if crt.Description == draft.Name { 87 | id = crt.ID 88 | break 89 | } 90 | } 91 | params := map[string]interface{}{ 92 | "key": fileAttachment{ 93 | FileName: "server.key", 94 | Reader: draft.Key, 95 | }, 96 | "cert": fileAttachment{ 97 | FileName: "server.crt", 98 | Reader: draft.Cert, 99 | }, 100 | "desc": draft.Name, 101 | } 102 | if draft.CA != nil { 103 | params["inter_cert"] = fileAttachment{ 104 | FileName: "ca.crt", 105 | Reader: draft.CA, 106 | } 107 | } 108 | if draft.AsDefault { 109 | params["as_default"] = "true" 110 | } 111 | if id != "" { 112 | params["id"] = id 113 | } 114 | return &info, cl.callAPI(ctx, "SYNO.Core.Certificate", "import", params, &info) 115 | } 116 | 117 | // DeleteCertByID deletes certificate by known ID (not name). 118 | func (cl *Client) DeleteCertByID(ctx context.Context, id string) (*ServerStatus, error) { 119 | var info ServerStatus 120 | if err := cl.Login(ctx); err != nil { 121 | return nil, fmt.Errorf("login: %w", err) 122 | } 123 | ids, err := json.Marshal([]string{id}) 124 | if err != nil { 125 | return nil, fmt.Errorf("marshal ids: %w", err) 126 | } 127 | return &info, cl.callAPI(ctx, "SYNO.Core.Certificate.CRT", "delete", map[string]interface{}{ 128 | "ids": string(ids), 129 | }, &info) 130 | } 131 | 132 | type CertUploadResult struct { 133 | CertificateID string `json:"id"` 134 | ServerStatus 135 | } 136 | 137 | type ServerStatus struct { 138 | ServerRestarted bool `json:"restart_httpd"` 139 | } 140 | -------------------------------------------------------------------------------- /pkg/client/certs_test.go: -------------------------------------------------------------------------------- 1 | package client_test 2 | 3 | import ( 4 | "context" 5 | "log/slog" 6 | "os" 7 | "path/filepath" 8 | "testing" 9 | "time" 10 | 11 | "github.com/reddec/syno-cli/pkg/client" 12 | 13 | "github.com/stretchr/testify/assert" 14 | "github.com/stretchr/testify/require" 15 | ) 16 | 17 | func TestClient_ListCerts(t *testing.T) { 18 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 19 | defer cancel() 20 | 21 | syno := client.New(client.FromEnv(environ())) 22 | list, err := syno.ListCerts(ctx) 23 | require.NoError(t, err) 24 | assert.NotEmpty(t, list) 25 | slog.Info("certificates fetched", "certs", list) 26 | } 27 | 28 | func TestClient_UploadCert(t *testing.T) { 29 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 30 | defer cancel() 31 | 32 | ca, err := os.Open(filepath.Join("../../test-data", "rootCA.pem")) 33 | require.NoError(t, err) 34 | defer ca.Close() 35 | 36 | key, err := os.Open(filepath.Join("../../test-data", "example.com-key.pem")) 37 | require.NoError(t, err) 38 | defer key.Close() 39 | 40 | cert, err := os.Open(filepath.Join("../../test-data", "example.com.pem")) 41 | require.NoError(t, err) 42 | defer cert.Close() 43 | 44 | syno := client.New(client.FromEnv(environ())) 45 | 46 | info, err := syno.UploadCert(ctx, client.NewCertificate{ 47 | Name: "example.com", 48 | Cert: cert, 49 | CA: ca, 50 | Key: key, 51 | }) 52 | require.NoError(t, err) 53 | assert.NotEmpty(t, info.CertificateID) 54 | } 55 | 56 | func TestClient_DeleteCertByID(t *testing.T) { 57 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 58 | defer cancel() 59 | 60 | ca, err := os.Open(filepath.Join("../../test-data", "rootCA.pem")) 61 | require.NoError(t, err) 62 | defer ca.Close() 63 | 64 | key, err := os.Open(filepath.Join("../../test-data", "example.com-key.pem")) 65 | require.NoError(t, err) 66 | defer key.Close() 67 | 68 | cert, err := os.Open(filepath.Join("../../test-data", "example.com.pem")) 69 | require.NoError(t, err) 70 | defer cert.Close() 71 | 72 | syno := client.New(client.FromEnv(environ())) 73 | 74 | info, err := syno.UploadCert(ctx, client.NewCertificate{ 75 | Name: "example.com", 76 | Cert: cert, 77 | CA: ca, 78 | Key: key, 79 | }) 80 | require.NoError(t, err) 81 | 82 | _, err = syno.DeleteCertByID(ctx, info.CertificateID) 83 | require.NoError(t, err) 84 | 85 | // check that removed 86 | list, err := syno.ListCerts(ctx) 87 | require.NoError(t, err) 88 | for _, item := range list { 89 | assert.NotEqual(t, info.CertificateID, item.ID) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /pkg/client/download_station.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "log/slog" 11 | "strconv" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | //go:generate go run github.com/abice/go-enum@v0.6.0 -values 17 | 18 | // FileType defines content type. 19 | // Extracted from 'allowfilters' in JS (download.js) 20 | // ENUM(unknown = "", auto, torrent, nzb, txt) 21 | type FileType string 22 | 23 | var ErrUnknownFileType = errors.New("unknown file type") 24 | 25 | const peekSize = 512 // should be enough for XML header, unless intentionally obfuscated 26 | 27 | type DownloadTask struct { 28 | URL []string // HTTP/FTP/magnet/ED2K links or the file path starting with a shared folder. 29 | File io.Reader // Optional. File (ex: torrent) uploading from client 30 | FileType FileType // Optional. Type of File. If not set, it will try automatically detect (which may fail, so better set it). 31 | Username string // Optional. Login username for remote resource (not NAS!) 32 | Password string // Optional. Login password for remote resource (not NAS!) 33 | UnzipPassword string // Optional. Password for unzipping download tasks 34 | Destination string // Optional. Download destination path starting with a shared folder 35 | } 36 | 37 | type DownloadTasks struct { 38 | Total int64 `json:"total"` 39 | Offset int64 `json:"offset"` 40 | Tasks []ScheduledTask `json:"tasks"` 41 | } 42 | 43 | type ScheduledTask struct { 44 | ID string `json:"id"` 45 | Type string `json:"type"` 46 | Username string `json:"username"` 47 | Title string `json:"title"` 48 | Size int64 `json:"size"` 49 | Status string `json:"status"` 50 | Additional struct { 51 | Detail struct { 52 | CreateTime int64 `json:"create_time"` 53 | Destination string `json:"destination"` 54 | Priority string `json:"priority"` 55 | URI string `json:"uri"` 56 | } `json:"detail"` 57 | } `json:"additional"` 58 | } 59 | 60 | // DownloadStation API. Enhanced by some undocumented API from JS. 61 | // 62 | // See https://global.download.synology.com/download/Document/Software/DeveloperGuide/Package/DownloadStation/All/enu/Synology_Download_Station_Web_API.pdf 63 | type DownloadStation struct { 64 | cl *Client 65 | } 66 | 67 | // List all download tasks in NAS. Implies 'detail' feature. Limit -1 means all. 68 | func (ds *DownloadStation) List(ctx context.Context, offset, limit int) (*DownloadTasks, error) { 69 | if err := ds.cl.Login(ctx); err != nil { 70 | return nil, fmt.Errorf("login: %w", err) 71 | } 72 | res, err := ds.cl.directCall(ctx, `SYNO.DownloadStation.Task`, `list`, []field{ 73 | {Name: "offset", Value: offset}, 74 | {Name: "limit", Value: limit}, 75 | {Name: "additional", Value: "detail"}, 76 | }) 77 | if err != nil { 78 | return nil, fmt.Errorf("call: %w", err) 79 | } 80 | defer res.Body.Close() 81 | var response struct { 82 | Data DownloadTasks `json:"data"` 83 | } 84 | if err := json.NewDecoder(res.Body).Decode(&response); err != nil { 85 | return nil, fmt.Errorf("decode response: %w", err) 86 | } 87 | return &response.Data, nil 88 | } 89 | 90 | // Download remote data from HTTP/FTP/magnet/ED2K links or the file path starting with a shared folder. 91 | // This is simplified version of Create. 92 | func (ds *DownloadStation) Download(ctx context.Context, destination string, urls ...string) error { 93 | return ds.Create(ctx, DownloadTask{ 94 | URL: urls, 95 | Destination: destination, 96 | }) 97 | } 98 | 99 | // Create download task in DownloadStation based on configuration. 100 | func (ds *DownloadStation) Create(ctx context.Context, task DownloadTask) error { 101 | if task.File != nil { 102 | return ds.createV2(ctx, task) 103 | } 104 | return ds.createV1(ctx, task) 105 | } 106 | 107 | // download task in DownloadStation based on configuration. Uses Synology legacy API 108 | func (ds *DownloadStation) createV1(ctx context.Context, task DownloadTask) error { 109 | slog.Debug("using v1 protocol") 110 | if err := ds.cl.Login(ctx); err != nil { 111 | return fmt.Errorf("login: %w", err) 112 | } 113 | var params []field 114 | if len(task.URL) > 0 { 115 | params = append(params, field{Name: "uri", Value: strings.Join(task.URL, ",")}) 116 | } 117 | params = setIfNotEmpty(params, "destination", task.Destination) 118 | params = setIfNotEmpty(params, "username", task.Username) 119 | params = setIfNotEmpty(params, "password", task.Password) 120 | params = setIfNotEmpty(params, "unzip_password", task.UnzipPassword) 121 | res, err := ds.cl.directCall(ctx, `SYNO.DownloadStation.Task`, `create`, params) 122 | if err != nil { 123 | return fmt.Errorf("call API: %w", err) 124 | } 125 | defer res.Body.Close() 126 | return nil 127 | } 128 | 129 | // download task in DownloadStation based on configuration. Uses Synology2 API. 130 | // Applies only for file uploads. 131 | func (ds *DownloadStation) createV2(ctx context.Context, task DownloadTask) error { 132 | slog.Debug("using v2 protocol") 133 | if err := ds.cl.Login(ctx); err != nil { 134 | return fmt.Errorf("login: %w", err) 135 | } 136 | var params []field 137 | params = setIfNotEmpty(params, "username", task.Username) 138 | params = setIfNotEmpty(params, "password", task.Password) 139 | if task.Destination != "" { 140 | value, err := json.Marshal(task.Destination) 141 | if err != nil { 142 | return fmt.Errorf("marshal destination: %w", err) 143 | } 144 | params = setIfNotEmpty(params, "destination", string(value)) 145 | } 146 | params = setIfNotEmpty(params, "unzip_password", task.UnzipPassword) 147 | params = append(params, field{Name: "type", Value: `"file"`}) 148 | 149 | if task.FileType == FileTypeAuto || task.FileType == FileTypeUnknown { 150 | // detect by sniffing the payload (as best as we can; we can just a few) 151 | peek := make([]byte, peekSize) 152 | n, err := io.ReadFull(task.File, peek) 153 | if errors.Is(err, io.ErrUnexpectedEOF) { 154 | err = nil // nps 155 | } 156 | if err != nil { 157 | return fmt.Errorf("peek file: %w", err) 158 | } 159 | peek = peek[:n] 160 | 161 | ft, err := detectType(peek) 162 | if err != nil { 163 | return fmt.Errorf("detect file type: %w", err) 164 | } 165 | slog.Debug("filetype automatically detected", "filetype", ft) 166 | task.FileType = ft 167 | task.File = io.MultiReader(bytes.NewReader(peek), task.File) 168 | } 169 | params = append(params, field{Name: "create_list", Value: `false`}) // required 170 | params = append(params, field{Name: "file", Value: `["` + task.FileType + `"]`}) 171 | params = append(params, field{Name: string(task.FileType), Value: fileAttachment{ 172 | FileName: generateFileName() + "." + string(task.FileType), 173 | Reader: task.File, 174 | }}) 175 | res, err := ds.cl.directCall(ctx, `SYNO.DownloadStation2.Task`, `create`, params) 176 | if err != nil { 177 | return fmt.Errorf("call API: %w", err) 178 | } 179 | defer res.Body.Close() 180 | return nil 181 | } 182 | 183 | func setIfNotEmpty(store []field, name string, value string) []field { 184 | if len(value) > 0 { 185 | return append(store, field{Name: name, Value: value}) 186 | } 187 | return store 188 | } 189 | 190 | func detectType(peek []byte) (FileType, error) { 191 | // it does it's best to detect but no guarantees. 192 | 193 | switch { 194 | case bytes.HasPrefix(peek, []byte("d8:announce")): 195 | return FileTypeTorrent, nil 196 | case bytes.Contains(peek, []byte("