diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ff2e47cc..c3c2b29d 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -3,110 +3,119 @@ name: Pumba CI on: push: branches: - - '*' + - '*' tags: - - '*' + - '*' paths-ignore: - - 'docs/**' - - 'deploy/**' - - '*.md' + - 'docs/**' + - 'deploy/**' + - '*.md' pull_request: branches: - - '*' + - '*' jobs: - full: - + build: + runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[skip ci]')" - + steps: - - uses: actions/checkout@v2 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v1 - - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v1 - - - name: list available platforms - run: echo ${{ steps.buildx.outputs.platforms }} - - - name: decide on tag - id: tagger - run: | - tag=$(echo "${{ github.ref }}" | sed -e 's/^refs\/heads\///g' -e 's/^refs\/tags\///g' -e 's/^refs\/pull\///g' -e 's/\/merge$//g' | sed -e 's/master/latest/g') - echo "::set-output name=tag::${tag}" - echo "::debug::docker image tag ${tag}" - - - name: prepare build arguments - id: prepare - run: | - args=$(echo "--progress=plain --build-arg=CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }} --build-arg=VCS_COMMIT_ID=${{ github.sha }} --build-arg=VCS_BRANCH_NAME=${{ steps.tagger.outputs.tag }} --build-arg=VCS_SLUG=${{ github.repository }} --file=docker/Dockerfile .") - echo "::debug::prepare build args ${args}" - echo "::set-output name=args::${args}" - - - name: cache Docker layers - uses: actions/cache@v2 - id: cache - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx- - - - name: build test images - run: | - echo "==> compile and test Go code" - DOCKER_BUILDKIT=1 docker build --tag=pumba/build-and-test \ - --target=build-and-test \ - ${{ steps.prepare.outputs.args }} - echo "==> prepare integration tests image" - DOCKER_BUILDKIT=1 docker build --tag pumba/integration-tests \ - --target=integration-tests \ - ${{ steps.prepare.outputs.args }} - - - name: run integration tests - run: | - docker buildx ls - docker run -i --rm --name integration-tests -v /var/run/docker.sock:/var/run/docker.sock pumba/integration-tests - - - name: upload coverage report - run: | - CI_BUILD_URL=https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks - docker run -i --rm --name upload-coverage -e CI_BUILD_URL=${CI_BUILD_URL} -e CI_BUILD_ID=${RUNNER_TRACKING_ID} pumba/build-and-test - - - name: login to DockerHub - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKER_ACCOUNT }} - password: ${{ secrets.DOCKER_TOKEN }} - - - name: prepare release image - if: github.event_name != 'pull_request' - env: - DOCKER_ORG: ${{ secrets.DOCKER_ORG }} - run: | - docker buildx build \ - --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 \ - --cache-from "type=local,src=/tmp/.buildx-cache" \ - --cache-to "type=local,dest=/tmp/.buildx-cache" \ - --output "type=image,push=true" \ - --tag=${DOCKER_ORG}/pumba:${{ steps.tagger.outputs.tag }} \ - ${{ steps.prepare.outputs.args }} - - - name: release to GitHub - if: github.event_name != 'pull_request' - run: | - if [[ "${{ steps.tagger.outputs.tag }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then \ - DOCKER_BUILDKIT=1 docker build \ - --tag pumba/github-release \ - --target=github-release \ - --build-arg=RELEASE=true \ - --build-arg=RELEASE_TAG=${{ steps.tagger.outputs.tag }} \ - --build-arg=TAG_MESSAGE="Draft Release" \ - --build-arg=GITHUB_TOKEN=${{ secrets.RELEASE_TOKEN }} \ - ${{ steps.prepare.outputs.args }}; \ - fi + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v1 + + - name: List available platforms + run: echo ${{ steps.buildx.outputs.platforms }} + + - name: Decide on tag + id: tagger + run: | + tag=$(echo "${{ github.ref }}" | sed -e 's/^refs\/heads\///g' -e 's/^refs\/tags\///g' -e 's/^refs\/pull\///g' -e 's/\/merge$//g' | sed -e 's/master/latest/g') + echo "::set-output name=tag::${tag}" + echo "::debug::docker image tag ${tag}" + + - name: Cache Docker layers + uses: actions/cache@v2 + id: cache + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + + - name: Build test image + env: + DOCKER_ORG: ${{ secrets.DOCKER_ORG }} + uses: docker/build-push-action@v2 + with: + file: docker/Dockerfile + context: . + build-args: | + CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }} + VCS_COMMIT_ID=${{ github.sha }} + VCS_BRANCH_NAME=${{ steps.tagger.outputs.tag }} + VCS_SLUG=${{ github.repository }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache + load: true + tags: pumba/build-and-test + target: build-and-test + + - name: Build integration test image + env: + DOCKER_ORG: ${{ secrets.DOCKER_ORG }} + uses: docker/build-push-action@v2 + with: + file: docker/Dockerfile + context: . + build-args: | + CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }} + VCS_COMMIT_ID=${{ github.sha }} + VCS_BRANCH_NAME=${{ steps.tagger.outputs.tag }} + VCS_SLUG=${{ github.repository }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache + load: true + tags: pumba/integration-tests + target: integration-tests + + - name: Run integration tests + run: | + docker run -i --rm --name integration-tests -v /var/run/docker.sock:/var/run/docker.sock pumba/integration-tests + + - name: Upload coverage report + run: | + CI_BUILD_URL=https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks + docker run -i --rm --name upload-coverage -e CI_BUILD_URL=${CI_BUILD_URL} -e CI_BUILD_ID=${RUNNER_TRACKING_ID} pumba/build-and-test + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_ACCOUNT }} + password: ${{ secrets.DOCKER_TOKEN }} + + - name: Build and push release image + if: github.event_name != 'pull_request' + env: + DOCKER_ORG: ${{ secrets.DOCKER_ORG }} + uses: docker/build-push-action@v2 + with: + file: docker/Dockerfile + context: . + platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 + build-args: | + CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }} + VCS_COMMIT_ID=${{ github.sha }} + VCS_BRANCH_NAME=${{ steps.tagger.outputs.tag }} + VCS_SLUG=${{ github.repository }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache + push: true + tags: ${DOCKER_ORG}/pumba:${{ steps.tagger.outputs.tag }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..a716eb52 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,64 @@ +name: release + +on: + workflow_dispatch: + push: + branches: + - 'master' + tags: + - '[0-9]+.[0-9]+.[0-9]+' + paths-ignore: + - 'docs/**' + - 'deploy/**' + - '*.md' + +jobs: + + release: + + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker buildx + id: buildx + uses: docker/setup-buildx-action@v1 + + - name: Decide on tag + id: tagger + run: | + tag=$(echo "${{ github.ref }}" | sed -e 's/^refs\/heads\///g' -e 's/^refs\/tags\///g' -e 's/^refs\/pull\///g' -e 's/\/merge$//g' | sed -e 's/master/latest/g') + echo "::set-output name=tag::${tag}" + echo "::debug::docker image tag ${tag}" + + - name: Cache Docker layers + uses: actions/cache@v2 + id: cache + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + + - name: Release to GitHub + if: startsWith(github.ref, 'refs/tags/') + uses: docker/build-push-action@v2 + with: + build-args: | + RELEASE=true + RELEASE_TAG=${{ steps.tagger.outputs.tag }} + RELEASE_TOKEN=${{ secrets.RELEASE_TOKEN }} + TAG_MESSAGE="Draft Release" + GITHUB_TOKEN=${{ secrets.RELEASE_TOKEN }} + CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }} + VCS_COMMIT_ID=${{ github.sha }} + VCS_BRANCH_NAME=${{ steps.tagger.outputs.tag }} + VCS_SLUG=${{ github.repository }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache + target: github-release diff --git a/.golangci.yml b/.golangci.yml index b817e52d..6698cfb2 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,14 +1,12 @@ linters-settings: govet: check-shadowing: true - golint: - min-confidence: 0 gocyclo: min-complexity: 15 maligned: suggest-new: true dupl: - threshold: 100 + threshold: 150 goconst: min-len: 2 min-occurrences: 2 @@ -32,7 +30,7 @@ linters-settings: - whyNoLint - wrapperFunc funlen: - lines: 100 + lines: 105 statements: 50 linters: @@ -40,28 +38,50 @@ linters: # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint disable-all: true enable: - - rowserrcheck + - asciicheck - bodyclose - deadcode - depguard - dogsled - dupl + - durationcheck - errcheck + - errname + - errorlint + - exhaustive + - exportloopref - funlen + - gci + # - gochecknoglobals TODO: remove globals from code + # - gochecknoinits TODO: remove main.init + - gocognit - goconst - gocritic - gocyclo + # - godox + - goerr113 - gofmt - goimports - - golint + - gomnd + - gomoddirectives - gosec - gosimple - govet + - goprintffuncname + - ifshort + - importas - ineffassign - - interfacer + - makezero - misspell - nakedret - - scopelint + # - nestif + - nilerr + - nolintlint + - prealloc + - predeclared + - promlinter + - revive + - rowserrcheck - staticcheck - structcheck - stylecheck @@ -70,15 +90,24 @@ linters: - unparam - unused - varcheck + - wastedassign - whitespace - - # don't enable: - # - gochecknoglobals - # - gocognit - # - godox - # - maligned - # - prealloc + - wrapcheck + # - wsl issues: - exclude: - - Using the variable on range scope `tt` in function literal \ No newline at end of file + exclude-rules: + - path: _test\.go + linters: + - funlen + - bodyclose + - gosec + - dupl + - gocognit + - goconst + - goerr113 + - path: mocks\*\.go + linters: + - wrapcheck + exclude: + - Using the variable on range scope `tt` in function literal diff --git a/Makefile b/Makefile index f64842ad..e5fb91fc 100644 --- a/Makefile +++ b/Makefile @@ -61,10 +61,8 @@ release: clean ; $(info $(M) building binaries for multiple os/arch...) @ ## Bui # Tools -setup-tools: setup-golint setup-golangci-lint setup-gocov setup-gocov-xml setup-go2xunit +setup-tools: setup-golangci-lint setup-gocov setup-gocov-xml setup-go2xunit -setup-golint: - $(GO) install golang.org/x/lint/golint@latest setup-golangci-lint: $(GO) install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.42.1 setup-gocov: @@ -74,7 +72,7 @@ setup-gocov-xml: setup-go2xunit: $(GO) install github.com/tebeka/go2xunit@latest setup-mockery: - $(GO) install github.com/vektra/mockery/v2/ + $(GO) get github.com/vektra/mockery/v2/ setup-ghr: $(GO) install github.com/tcnksm/ghr@latest @@ -129,13 +127,9 @@ integration-tests: build ; $(info $(M) running integration tests with bats...) @ $Q PATH=$(BIN)/$(dir $(MODULE)):$(PATH) pumba --version $Q PATH=$(BIN)/$(dir $(MODULE)):$(PATH) $(BATS) tests -.PHONY: golangci-lint -golangci-lint: setup-golangci-lint; $(info $(M) running golangci-lint...) @ ## Run golangci-lint - $Q $(GOLANGCI_LINT) run -v -c $(GOLANGCI_LINT_CONFIG) ./... - .PHONY: lint -lint: setup-golint; $(info $(M) running golint...) @ ## Run golint - $Q $(GOLINT) -set_exit_status $(PKGS) +lint: setup-golangci-lint; $(info $(M) running golangci-lint...) @ ## Run golangci-lint + $Q $(GOLANGCI_LINT) run -v -c $(GOLANGCI_LINT_CONFIG) ./... .PHONY: fmt fmt: ; $(info $(M) running gofmt...) @ ## Run gofmt on all source files @@ -146,9 +140,9 @@ fmt: ; $(info $(M) running gofmt...) @ ## Run gofmt on all source files mocks: setup-mockery; $(info $(M) generating mocks...) @ ## Run mockery $Q $(GOMOCK) --dir pkg/chaos/docker --all $Q $(GOMOCK) --dir pkg/container --inpackage --all - $Q $(GOMOCK) --dir $(call source_of,github.com/docker/engine)/client --name ContainerAPIClient - $Q $(GOMOCK) --dir $(call source_of,github.com/docker/engine)/client --name ImageAPIClient - $Q $(GOMOCK) --dir $(call source_of,github.com/docker/engine)/client --name APIClient + $Q $(GOMOCK) --dir $(call source_of,github.com/docker/docker)/client --name ContainerAPIClient + $Q $(GOMOCK) --dir $(call source_of,github.com/docker/docker)/client --name ImageAPIClient + $Q $(GOMOCK) --dir $(call source_of,github.com/docker/docker)/client --name APIClient # generate CHANGELOG.md changelog file .PHONY: changelog diff --git a/VERSION b/VERSION index e7c7d3cc..ac39a106 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.7.8 +0.9.0 diff --git a/cmd/main.go b/cmd/main.go index be8cdbc7..5bc37b98 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -17,11 +17,10 @@ import ( netemCmd "github.com/alexei-led/pumba/pkg/chaos/netem/cmd" stressCmd "github.com/alexei-led/pumba/pkg/chaos/stress/cmd" "github.com/alexei-led/pumba/pkg/container" + "github.com/johntdyer/slackrus" + "github.com/pkg/errors" log "github.com/sirupsen/logrus" - "github.com/urfave/cli" - - "github.com/johntdyer/slackrus" ) var ( @@ -46,10 +45,10 @@ var ( ) const ( - // Re2Prefix re2 regexp string prefix - Re2Prefix = "re2:" - // DefaultInterface default network interface - DefaultInterface = "eth0" + // re2 regexp string prefix + re2Prefix = "re2:" + // default network interface + defaultInterface = "eth0" ) func init() { @@ -79,7 +78,7 @@ func main() { } app.EnableBashCompletion = true app.Usage = "Pumba is a resilience testing tool, that helps applications tolerate random Docker container failures: process, network and performance." - app.ArgsUsage = fmt.Sprintf("containers (name, list of names, or RE2 regex if prefixed with %q)", Re2Prefix) + app.ArgsUsage = fmt.Sprintf("containers (name, list of names, or RE2 regex if prefixed with %q)", re2Prefix) app.Before = before app.Commands = initializeCLICommands() app.Flags = []cli.Flag{ @@ -133,7 +132,7 @@ func main() { Usage: "Slack channel (default #pumba)", Value: "#pumba", }, - cli.StringFlag{ + cli.DurationFlag{ Name: "interval, i", Usage: "recurrent interval for chaos command; use with optional unit suffix: 'ms/s/m/h'", }, @@ -200,7 +199,10 @@ func before(c *cli.Context) error { } // create new Docker client chaos.DockerClient, err = container.NewClient(c.GlobalString("host"), tlsCfg) - return err + if err != nil { + return errors.Wrap(err, "could not create Docker client") + } + return nil } func handleSignals() context.Context { @@ -232,7 +234,7 @@ func tlsConfig(c *cli.Context) (*tls.Config, error) { if c.GlobalBool("tls") || c.GlobalBool("tlsverify") { tlsCfg = &tls.Config{ - InsecureSkipVerify: !c.GlobalBool("tlsverify"), + InsecureSkipVerify: !c.GlobalBool("tlsverify"), //nolint:gosec } // Load CA cert @@ -241,7 +243,7 @@ func tlsConfig(c *cli.Context) (*tls.Config, error) { if strings.HasPrefix(caCertFlag, "/") { caCert, err = ioutil.ReadFile(caCertFlag) if err != nil { - return nil, err + return nil, errors.Wrap(err, "unable to read CA certificate") } } else { caCert = []byte(caCertFlag) @@ -257,12 +259,12 @@ func tlsConfig(c *cli.Context) (*tls.Config, error) { if strings.HasPrefix(certFlag, "/") && strings.HasPrefix(keyFlag, "/") { cert, err = tls.LoadX509KeyPair(certFlag, keyFlag) if err != nil { - return nil, err + return nil, errors.Wrap(err, "unable to load client certificate") } } else { cert, err = tls.X509KeyPair([]byte(certFlag), []byte(keyFlag)) if err != nil { - return nil, err + return nil, errors.Wrap(err, "unable to load client certificate") } } tlsCfg.Certificates = []tls.Certificate{cert} @@ -283,26 +285,26 @@ func initializeCLICommands() []cli.Command { { Name: "netem", Flags: []cli.Flag{ - cli.StringFlag{ + cli.DurationFlag{ Name: "duration, d", Usage: "network emulation duration; should be smaller than recurrent interval; use with optional unit suffix: 'ms/s/m/h'", }, cli.StringFlag{ Name: "interface, i", Usage: "network interface to apply delay on", - Value: DefaultInterface, + Value: defaultInterface, }, cli.StringSliceFlag{ Name: "target, t", Usage: "target IP filter; supports multiple IPs; supports CIDR notation", }, cli.StringFlag{ - Name: "egressPort", - Usage: "target port filter for egress, or sport; supports multiple ports;", + Name: "egress-port, egressPort", + Usage: "target port filter for egress, or sport; supports multiple ports (comma-separated)", }, cli.StringFlag{ - Name: "ingressPort", - Usage: "target port filter for ingress, or dport; supports multiple ports;", + Name: "ingress-port, ingressPort", + Usage: "target port filter for ingress, or dport; supports multiple ports (comma-separated)", }, cli.StringFlag{ Name: "tc-image", @@ -310,11 +312,11 @@ func initializeCLICommands() []cli.Command { }, cli.BoolTFlag{ Name: "pull-image", - Usage: "try to pull tc-image", + Usage: "force pull tc-image", }, }, Usage: "emulate the properties of wide area networks", - ArgsUsage: fmt.Sprintf("containers (name, list of names, or RE2 regex if prefixed with %q", Re2Prefix), + ArgsUsage: fmt.Sprintf("containers (name, list of names, or RE2 regex if prefixed with %q", re2Prefix), Description: "delay, loss, duplicate and re-order (run 'netem') packets, and limit the bandwidth, to emulate different network problems", Subcommands: []cli.Command{ *netemCmd.NewDelayCLICommand(topContext), diff --git a/docker/Dockerfile b/docker/Dockerfile index aca7e48d..29df9213 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -3,7 +3,7 @@ # # ----- Go Builder Image ------ # -FROM --platform=${BUILDPLATFORM} golang:1.17 AS builder +FROM golang:1.17 AS builder # curl git bash RUN apt-get update && apt-get install -y --no-install-recommends \ @@ -15,7 +15,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # # ----- Build and Test Image ----- # -FROM --platform=${BUILDPLATFORM} builder as build-and-test +FROM builder as build-and-test # set working directory RUN mkdir -p /go/src/pumba @@ -33,7 +33,8 @@ RUN --mount=type=cache,target=/root/.cache/go-build make dependency COPY mocks ./mocks COPY cmd ./cmd COPY pkg ./pkg -COPY VERSION ./VERSION +COPY .golangci.yml . +COPY VERSION . # run lint, test race and calculate coverage RUN --mount=type=cache,target=/root/.cache/go-build make lint test-race test-coverage @@ -66,25 +67,26 @@ CMD ["./codecov.sh", "-e", "VCS_COMMIT_ID,VCS_BRANCH_NAME,VCS_SLUG,CI_BUILD_ID,C # # ------ Pumba Integration Tests ------ # -FROM alpine:3.13 as integration-tests +FROM bats/bats:1.5.0 as integration-tests # install required packages -RUN apk add --no-cache bash bats docker iproute2 +RUN apk add --no-cache docker iproute2 # copy bats tests -COPY ./tests /tests +COPY ./tests /code/tests COPY VERSION . # copy compiled binary COPY --from=build-and-test /go/src/pumba/.bin/github.com/alexei-led/pumba /usr/local/bin/pumba # mount docker.sock and run pumba tests -CMD [ "bash", "-c", "[ -e /var/run/docker.sock ] && bats /tests" ] +ENTRYPOINT [ "bash", "-c" ] +CMD [ "[ -e /var/run/docker.sock ] && bats --print-output-on-failure /tests" ] # # ------ Pumba GitHub Release ------ # -FROM --platform=${BUILDPLATFORM} build-and-test as github-release +FROM build-and-test as github-release # build argument to secify if to create a GitHub release ARG DEBUG=false @@ -109,7 +111,7 @@ RUN --mount=type=cache,target=/root/.cache/go-build if $RELEASE; then make githu # # ------ get latest CA certificates # -FROM alpine:3.13 as certs +FROM alpine:3.14 as certs RUN apk --update add ca-certificates @@ -121,7 +123,7 @@ FROM scratch # copy CA certificates COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt -# this is the last commabd since it's never cached +# this is the last command since it's never cached COPY --from=build-and-test /go/src/pumba/.bin/github.com/alexei-led/pumba /pumba ENTRYPOINT ["/pumba"] \ No newline at end of file diff --git a/go.mod b/go.mod index b9d016c4..2ede1064 100644 --- a/go.mod +++ b/go.mod @@ -1,46 +1,66 @@ module github.com/alexei-led/pumba require ( - github.com/docker/docker v1.13.1 + github.com/docker/docker v20.10.10+incompatible github.com/docker/go-connections v0.4.0 - github.com/johntdyer/slackrus v0.0.0-20180518184837-f7aae3243a07 + github.com/johntdyer/slackrus v0.0.0-20210521205746-42486fb4c48c github.com/pkg/errors v0.9.1 - github.com/sirupsen/logrus v1.7.0 - github.com/stretchr/testify v1.6.1 - github.com/urfave/cli v1.22.4 - golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 + github.com/sirupsen/logrus v1.8.1 + github.com/stretchr/testify v1.7.0 + github.com/urfave/cli v1.22.5 + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c ) require ( - github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.5.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect + github.com/Microsoft/go-winio v0.5.1 // indirect + github.com/containerd/containerd v1.5.7 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/distribution v2.7.1+incompatible // indirect github.com/docker/go-units v0.4.0 // indirect + github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.4.3 // indirect + github.com/golang/protobuf v1.5.0 // indirect github.com/gorilla/mux v1.8.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22 // indirect - github.com/kr/pretty v0.1.0 // indirect + github.com/magiconair/properties v1.8.1 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.1.2 // indirect + github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.1 // indirect + github.com/pelletier/go-toml v1.8.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/russross/blackfriday/v2 v2.0.1 // indirect + github.com/rs/zerolog v1.18.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect - github.com/stretchr/objx v0.1.1 // indirect - golang.org/x/net v0.0.0-20210917163549-3c21e5b27794 // indirect + github.com/spf13/afero v1.2.2 // indirect + github.com/spf13/cast v1.3.0 // indirect + github.com/spf13/cobra v1.0.0 // indirect + github.com/spf13/jwalterweatherman v1.0.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.7.0 // indirect + github.com/stretchr/objx v0.3.0 // indirect + github.com/subosito/gotenv v1.2.0 // indirect + github.com/vektra/mockery/v2 v2.9.4 // indirect + golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect + golang.org/x/mod v0.3.0 // indirect + golang.org/x/net v0.0.0-20211111083644-e5c967477495 // indirect golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect + golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect + golang.org/x/text v0.3.6 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect - google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect - google.golang.org/grpc v1.40.0 // indirect - google.golang.org/protobuf v1.25.0 // indirect - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect - gotest.tools v2.2.0+incompatible // indirect + golang.org/x/tools v0.0.0-20210106214847-113979e3529a // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a // indirect + google.golang.org/grpc v1.42.0 // indirect + google.golang.org/protobuf v1.26.0 // indirect + gopkg.in/ini.v1 v1.51.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) -replace github.com/docker/docker => github.com/docker/engine v17.12.0-ce-rc1.0.20190717161051-705d9623b7c1+incompatible - go 1.17 diff --git a/go.sum b/go.sum index 01af30aa..a8c1743c 100644 --- a/go.sum +++ b/go.sum @@ -1,46 +1,325 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= +github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU= -github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= +github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= +github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= +github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= +github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= +github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= +github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= +github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= +github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= +github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= +github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= +github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= +github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= +github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= +github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= +github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= +github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= +github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= +github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= +github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= +github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= +github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= +github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= +github.com/containerd/containerd v1.5.7 h1:rQyoYtj4KddB3bxG6SAqd4+08gePNyJjRqvOIfV3rkM= +github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= +github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= +github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= +github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= +github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= +github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= +github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= +github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= +github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= +github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= +github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= +github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= +github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= +github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= +github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= +github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= +github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= +github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= +github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= +github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= +github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= +github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= +github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= +github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= +github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= +github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= +github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/engine v17.12.0-ce-rc1.0.20190717161051-705d9623b7c1+incompatible h1:4Pnn+RsurVEiBbmqlRtzh77HLMiP4NaaqRHOOK4aPj8= -github.com/docker/engine v17.12.0-ce-rc1.0.20190717161051-705d9623b7c1+incompatible/go.mod h1:3CPr2caMgTHxxIAZgEMd3uLYPDlRvPqCpyeRf6ncPcY= +github.com/docker/docker v20.10.10+incompatible h1:GKkP0T7U4ks6X3lmmHKC2QDprnpRJor2Z5a8m62R9ZM= +github.com/docker/docker v20.10.10+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= +github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= +github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -48,136 +327,658 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22 h1:jKUP9TQ0c7X3w6+IPyMit07RE42MtTWNd77sN2cHngQ= github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22/go.mod h1:u0Jo4f2dNlTJeeOywkM6bLwxq6gC3pZ9rEFHn3AhTdk= github.com/johntdyer/slackrus v0.0.0-20180518184837-f7aae3243a07 h1:+kBG/8rjCa6vxJZbUjAiE4MQmBEBYc8nLEb51frnvBY= github.com/johntdyer/slackrus v0.0.0-20180518184837-f7aae3243a07/go.mod h1:j1kV/8f3jowErEq4XyeypkCdvg5EeHkf0YCKCcq5Ybo= +github.com/johntdyer/slackrus v0.0.0-20210521205746-42486fb4c48c h1:4eD0DM7IEOQI5egyzKdPXncs61BxMtYwwzJPCRQFU0Y= +github.com/johntdyer/slackrus v0.0.0-20210521205746-42486fb4c48c/go.mod h1:j1kV/8f3jowErEq4XyeypkCdvg5EeHkf0YCKCcq5Ybo= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= +github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= +github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= +github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= +github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.18.0 h1:CbAm3kP2Tptby1i9sYy2MGRg0uxIN9cyDb59Ys7W8z8= +github.com/rs/zerolog v1.18.0/go.mod h1:9nvC1axdVrAHcu/s9taAVfBuIdTZLVQmKQyvrUjF5+I= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= +github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As= +github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= +github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vektra/mockery/v2 v2.9.4 h1:ZjpYWY+YLkDIKrKtFnYPxJax10lktcUapWZtOSg4g7g= +github.com/vektra/mockery/v2 v2.9.4/go.mod h1:2gU4Cf/f8YyC8oEaSXfCnZBMxMjMl/Ko205rlP0fO90= +github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210917163549-3c21e5b27794 h1:pOaRGvJk+MpHIfe37zcmbwolJplrAmLKmvggJVLkYl8= -golang.org/x/net v0.0.0-20210917163549-3c21e5b27794/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211111083644-e5c967477495 h1:cjxxlQm6d4kYbhpZ2ghvmI8xnq0AG+jXmzrhzfkyu5A= +golang.org/x/net v0.0.0-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200323144430-8dcfad9e016e/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a h1:CB3a9Nez8M13wwlr/E2YtwoU+qYHKfC+JrDa45RXXoQ= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a h1:pOwg4OoaRYScjmR4LlLgdtnyoHYTSAVhhqe5uPdpII8= +google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -186,16 +987,87 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= +k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= +k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= +k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= +k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= +k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= +k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= +k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= +k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= +k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= +k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= +k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= +k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= +k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= +k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= +k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/mocks/APIClient.go b/mocks/APIClient.go index f6321271..e7d756d5 100644 --- a/mocks/APIClient.go +++ b/mocks/APIClient.go @@ -31,6 +31,8 @@ import ( types "github.com/docker/docker/api/types" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + volume "github.com/docker/docker/api/types/volume" ) @@ -299,20 +301,20 @@ func (_m *APIClient) ContainerCommit(ctx context.Context, _a1 string, options ty return r0, r1 } -// ContainerCreate provides a mock function with given fields: ctx, config, hostConfig, networkingConfig, containerName -func (_m *APIClient) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error) { - ret := _m.Called(ctx, config, hostConfig, networkingConfig, containerName) +// ContainerCreate provides a mock function with given fields: ctx, config, hostConfig, networkingConfig, platform, containerName +func (_m *APIClient) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *v1.Platform, containerName string) (container.ContainerCreateCreatedBody, error) { + ret := _m.Called(ctx, config, hostConfig, networkingConfig, platform, containerName) var r0 container.ContainerCreateCreatedBody - if rf, ok := ret.Get(0).(func(context.Context, *container.Config, *container.HostConfig, *network.NetworkingConfig, string) container.ContainerCreateCreatedBody); ok { - r0 = rf(ctx, config, hostConfig, networkingConfig, containerName) + if rf, ok := ret.Get(0).(func(context.Context, *container.Config, *container.HostConfig, *network.NetworkingConfig, *v1.Platform, string) container.ContainerCreateCreatedBody); ok { + r0 = rf(ctx, config, hostConfig, networkingConfig, platform, containerName) } else { r0 = ret.Get(0).(container.ContainerCreateCreatedBody) } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, *container.Config, *container.HostConfig, *network.NetworkingConfig, string) error); ok { - r1 = rf(ctx, config, hostConfig, networkingConfig, containerName) + if rf, ok := ret.Get(1).(func(context.Context, *container.Config, *container.HostConfig, *network.NetworkingConfig, *v1.Platform, string) error); ok { + r1 = rf(ctx, config, hostConfig, networkingConfig, platform, containerName) } else { r1 = ret.Error(1) } @@ -694,6 +696,27 @@ func (_m *APIClient) ContainerStats(ctx context.Context, _a1 string, stream bool return r0, r1 } +// ContainerStatsOneShot provides a mock function with given fields: ctx, _a1 +func (_m *APIClient) ContainerStatsOneShot(ctx context.Context, _a1 string) (types.ContainerStats, error) { + ret := _m.Called(ctx, _a1) + + var r0 types.ContainerStats + if rf, ok := ret.Get(0).(func(context.Context, string) types.ContainerStats); ok { + r0 = rf(ctx, _a1) + } else { + r0 = ret.Get(0).(types.ContainerStats) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // ContainerStop provides a mock function with given fields: ctx, _a1, timeout func (_m *APIClient) ContainerStop(ctx context.Context, _a1 string, timeout *time.Duration) error { ret := _m.Called(ctx, _a1, timeout) diff --git a/mocks/ChaosCommand.go b/mocks/ChaosCommand.go index 0d51a74b..0724ecd1 100644 --- a/mocks/ChaosCommand.go +++ b/mocks/ChaosCommand.go @@ -22,5 +22,5 @@ func (_m *Command) Run(ctx context.Context, random bool) error { r0 = ret.Error(0) } - return r0 + return r0 //nolint:wrapcheck } diff --git a/mocks/ContainerAPIClient.go b/mocks/ContainerAPIClient.go index d7a979ac..54270618 100644 --- a/mocks/ContainerAPIClient.go +++ b/mocks/ContainerAPIClient.go @@ -18,6 +18,8 @@ import ( time "time" types "github.com/docker/docker/api/types" + + v1 "github.com/opencontainers/image-spec/specs-go/v1" ) // ContainerAPIClient is an autogenerated mock type for the ContainerAPIClient type @@ -67,20 +69,20 @@ func (_m *ContainerAPIClient) ContainerCommit(ctx context.Context, _a1 string, o return r0, r1 } -// ContainerCreate provides a mock function with given fields: ctx, config, hostConfig, networkingConfig, containerName -func (_m *ContainerAPIClient) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error) { - ret := _m.Called(ctx, config, hostConfig, networkingConfig, containerName) +// ContainerCreate provides a mock function with given fields: ctx, config, hostConfig, networkingConfig, platform, containerName +func (_m *ContainerAPIClient) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *v1.Platform, containerName string) (container.ContainerCreateCreatedBody, error) { + ret := _m.Called(ctx, config, hostConfig, networkingConfig, platform, containerName) var r0 container.ContainerCreateCreatedBody - if rf, ok := ret.Get(0).(func(context.Context, *container.Config, *container.HostConfig, *network.NetworkingConfig, string) container.ContainerCreateCreatedBody); ok { - r0 = rf(ctx, config, hostConfig, networkingConfig, containerName) + if rf, ok := ret.Get(0).(func(context.Context, *container.Config, *container.HostConfig, *network.NetworkingConfig, *v1.Platform, string) container.ContainerCreateCreatedBody); ok { + r0 = rf(ctx, config, hostConfig, networkingConfig, platform, containerName) } else { r0 = ret.Get(0).(container.ContainerCreateCreatedBody) } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, *container.Config, *container.HostConfig, *network.NetworkingConfig, string) error); ok { - r1 = rf(ctx, config, hostConfig, networkingConfig, containerName) + if rf, ok := ret.Get(1).(func(context.Context, *container.Config, *container.HostConfig, *network.NetworkingConfig, *v1.Platform, string) error); ok { + r1 = rf(ctx, config, hostConfig, networkingConfig, platform, containerName) } else { r1 = ret.Error(1) } @@ -462,6 +464,27 @@ func (_m *ContainerAPIClient) ContainerStats(ctx context.Context, _a1 string, st return r0, r1 } +// ContainerStatsOneShot provides a mock function with given fields: ctx, _a1 +func (_m *ContainerAPIClient) ContainerStatsOneShot(ctx context.Context, _a1 string) (types.ContainerStats, error) { + ret := _m.Called(ctx, _a1) + + var r0 types.ContainerStats + if rf, ok := ret.Get(0).(func(context.Context, string) types.ContainerStats); ok { + r0 = rf(ctx, _a1) + } else { + r0 = ret.Get(0).(types.ContainerStats) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // ContainerStop provides a mock function with given fields: ctx, _a1, timeout func (_m *ContainerAPIClient) ContainerStop(ctx context.Context, _a1 string, timeout *time.Duration) error { ret := _m.Called(ctx, _a1, timeout) diff --git a/pkg/chaos/command.go b/pkg/chaos/command.go index 14ab2b10..a0d5d2d9 100644 --- a/pkg/chaos/command.go +++ b/pkg/chaos/command.go @@ -6,8 +6,7 @@ import ( "time" "github.com/alexei-led/pumba/pkg/container" - "github.com/alexei-led/pumba/pkg/util" - + "github.com/pkg/errors" log "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -27,9 +26,45 @@ type Command interface { Run(ctx context.Context, random bool) error } -// GetNamesOrPattern get names list of filter pattern from command line -func GetNamesOrPattern(c *cli.Context) ([]string, string) { - names := []string{} +// GlobalParams global parameters passed through CLI flags +type GlobalParams struct { + Random bool + Labels []string + Pattern string + Names []string + Interval time.Duration + DryRun bool + SkipErrors bool +} + +// ParseGlobalParams parse global parameters +func ParseGlobalParams(c *cli.Context) (*GlobalParams, error) { + // get random flag + random := c.GlobalBool("random") + // get labels + labels := c.GlobalStringSlice("label") + // get dry-run mode + dryRun := c.GlobalBool("dry-run") + // get skip error flag + skipError := c.GlobalBool("skip-error") + // get names or pattern + names, pattern := getNamesOrPattern(c) + // get global chaos interval + interval := c.GlobalDuration("interval") + return &GlobalParams{ + Random: random, + Labels: labels, + Pattern: pattern, + Names: names, + DryRun: dryRun, + SkipErrors: skipError, + Interval: interval, + }, nil +} + +// get names list of filter pattern from command line +func getNamesOrPattern(c *cli.Context) ([]string, string) { + var names []string pattern := "" // get container names or pattern: no Args means ALL containers if c.Args().Present() { @@ -52,19 +87,13 @@ func GetNamesOrPattern(c *cli.Context) ([]string, string) { } // RunChaosCommand run chaos command in go routine -func RunChaosCommand(topContext context.Context, command Command, intervalStr string, random, skipError bool) error { - // parse interval - interval, err := util.GetIntervalValue(intervalStr) - if err != nil { - return err - } - +func RunChaosCommand(topContext context.Context, command Command, params *GlobalParams) error { // create Time channel for specified interval var tick <-chan time.Time - if interval == 0 { - tick = time.NewTimer(interval).C + if params.Interval == 0 { + tick = time.NewTimer(params.Interval).C } else { - tick = time.NewTicker(interval).C + tick = time.NewTicker(params.Interval).C } // handle the 'chaos' command @@ -74,9 +103,9 @@ func RunChaosCommand(topContext context.Context, command Command, intervalStr st // run chaos command for { // run chaos function - if err := command.Run(ctx, random); err != nil { - if !skipError { - return err + if err := command.Run(ctx, params.Random); err != nil { + if !params.SkipErrors { + return errors.Wrap(err, "error running chaos command") } log.WithError(err).Warn("skipping error") } @@ -85,7 +114,7 @@ func RunChaosCommand(topContext context.Context, command Command, intervalStr st case <-topContext.Done(): return nil // not to leak the goroutine case <-tick: - if interval == 0 { + if params.Interval == 0 { return nil // not to leak the goroutine } log.Debug("next chaos execution (tick) ...") diff --git a/pkg/chaos/docker/cmd/exec.go b/pkg/chaos/docker/cmd/exec.go index 13987bb5..5183910c 100644 --- a/pkg/chaos/docker/cmd/exec.go +++ b/pkg/chaos/docker/cmd/exec.go @@ -4,10 +4,10 @@ import ( "context" "fmt" - "github.com/urfave/cli" - "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/chaos/docker" + "github.com/pkg/errors" + "github.com/urfave/cli" ) type execContext struct { @@ -40,27 +40,21 @@ func NewExecCLICommand(ctx context.Context) *cli.Command { // EXEC Command func (cmd *execContext) exec(c *cli.Context) error { - // get random - random := c.GlobalBool("random") - // get labels - labels := c.GlobalStringSlice("label") - // get dry-run mode - dryRun := c.GlobalBool("dry-run") - // get skip error flag - skipError := c.GlobalBool("skip-error") - // get interval - interval := c.GlobalString("interval") - // get names or pattern - names, pattern := chaos.GetNamesOrPattern(c) + // parse common chaos flags + params, err := chaos.ParseGlobalParams(c) + if err != nil { + return errors.Wrap(err, "error parsing global parameters") + } // get command command := c.String("command") // get limit for number of containers to exec limit := c.Int("limit") // init exec command - execCommand, err := docker.NewExecCommand(chaos.DockerClient, names, pattern, labels, command, limit, dryRun) + execCommand := docker.NewExecCommand(chaos.DockerClient, params, command, limit) + // run exec command + err = chaos.RunChaosCommand(cmd.context, execCommand, params) if err != nil { - return err + return errors.Wrap(err, "could not run exec command") } - // run exec command - return chaos.RunChaosCommand(cmd.context, execCommand, interval, random, skipError) + return nil } diff --git a/pkg/chaos/docker/cmd/kill.go b/pkg/chaos/docker/cmd/kill.go index 09446d65..ba1ac506 100644 --- a/pkg/chaos/docker/cmd/kill.go +++ b/pkg/chaos/docker/cmd/kill.go @@ -4,10 +4,10 @@ import ( "context" "fmt" - "github.com/urfave/cli" - "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/chaos/docker" + "github.com/pkg/errors" + "github.com/urfave/cli" ) type killContext struct { @@ -40,27 +40,24 @@ func NewKillCLICommand(ctx context.Context) *cli.Command { // KILL Command func (cmd *killContext) kill(c *cli.Context) error { - // get random - random := c.GlobalBool("random") - // get labels - labels := c.GlobalStringSlice("label") - // get dry-run mode - dryRun := c.GlobalBool("dry-run") - // get skip error flag - skipError := c.GlobalBool("skip-error") - // get interval - interval := c.GlobalString("interval") - // get names or pattern - names, pattern := chaos.GetNamesOrPattern(c) + // parse common chaos flags + params, err := chaos.ParseGlobalParams(c) + if err != nil { + return errors.Wrap(err, "error parsing global parameters") + } // get signal signal := c.String("signal") // get limit for number of containers to kill limit := c.Int("limit") // init kill command - killCommand, err := docker.NewKillCommand(chaos.DockerClient, names, pattern, labels, signal, limit, dryRun) + killCommand, err := docker.NewKillCommand(chaos.DockerClient, params, signal, limit) if err != nil { - return err + return errors.Wrap(err, "could not create kill command") } // run kill command - return chaos.RunChaosCommand(cmd.context, killCommand, interval, random, skipError) + err = chaos.RunChaosCommand(cmd.context, killCommand, params) + if err != nil { + return errors.Wrap(err, "could not kill containers") + } + return nil } diff --git a/pkg/chaos/docker/cmd/pause.go b/pkg/chaos/docker/cmd/pause.go index c02033a4..b3c9dfc2 100644 --- a/pkg/chaos/docker/cmd/pause.go +++ b/pkg/chaos/docker/cmd/pause.go @@ -4,10 +4,10 @@ import ( "context" "fmt" - "github.com/urfave/cli" - "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/chaos/docker" + "github.com/pkg/errors" + "github.com/urfave/cli" ) type pauseContext struct { @@ -39,27 +39,24 @@ func NewPauseCLICommand(ctx context.Context) *cli.Command { // PAUSE Command func (cmd *pauseContext) pause(c *cli.Context) error { - // get random flag - random := c.GlobalBool("random") - // get dry-run mode - dryRun := c.GlobalBool("dry-run") - // get skip error flag - skipError := c.GlobalBool("skip-error") - // get labels - labels := c.GlobalStringSlice("label") - // get global chaos interval - interval := c.GlobalString("interval") + // parse common chaos flags + params, err := chaos.ParseGlobalParams(c) + if err != nil { + return errors.Wrap(err, "error parsing global parameters") + } // get limit for number of containers to kill limit := c.Int("limit") - // get names or pattern - names, pattern := chaos.GetNamesOrPattern(c) - // get chaos command duration - duration := c.String("duration") + // get duration + duration := c.Duration("duration") + if duration == 0 { + return errors.New("unset or invalid duration value") + } // init pause command - pauseCommand, err := docker.NewPauseCommand(chaos.DockerClient, names, pattern, labels, interval, duration, limit, dryRun) + pauseCommand := docker.NewPauseCommand(chaos.DockerClient, params, duration, limit) + // run pause command + err = chaos.RunChaosCommand(cmd.context, pauseCommand, params) if err != nil { - return err + return errors.Wrap(err, "error running pause command") } - // run pause command - return chaos.RunChaosCommand(cmd.context, pauseCommand, interval, random, skipError) + return nil } diff --git a/pkg/chaos/docker/cmd/remove.go b/pkg/chaos/docker/cmd/remove.go index c4371062..e0565079 100644 --- a/pkg/chaos/docker/cmd/remove.go +++ b/pkg/chaos/docker/cmd/remove.go @@ -4,10 +4,10 @@ import ( "context" "fmt" - "github.com/urfave/cli" - "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/chaos/docker" + "github.com/pkg/errors" + "github.com/urfave/cli" ) type removeContext struct { @@ -47,18 +47,11 @@ func NewRemoveCLICommand(ctx context.Context) *cli.Command { // REMOVE Command func (cmd *removeContext) remove(c *cli.Context) error { - // get random - random := c.GlobalBool("random") - // get labels - labels := c.GlobalStringSlice("label") - // get dry-run mode - dryRun := c.GlobalBool("dry-run") - // get skip error flag - skipError := c.GlobalBool("skip-error") - // get interval - interval := c.GlobalString("interval") - // get names or pattern - names, pattern := chaos.GetNamesOrPattern(c) + // parse common chaos flags + params, err := chaos.ParseGlobalParams(c) + if err != nil { + return errors.Wrap(err, "error parsing global parameters") + } // get force flag force := c.BoolT("force") // get links flag @@ -68,10 +61,11 @@ func (cmd *removeContext) remove(c *cli.Context) error { // get limit for number of containers to remove limit := c.Int("limit") // init remove command - removeCommand, err := docker.NewRemoveCommand(chaos.DockerClient, names, pattern, labels, force, links, volumes, limit, dryRun) + removeCommand := docker.NewRemoveCommand(chaos.DockerClient, params, force, links, volumes, limit) + // run remove command + err = chaos.RunChaosCommand(cmd.context, removeCommand, params) if err != nil { - return err + return errors.Wrap(err, "error running remove command") } - // run remove command - return chaos.RunChaosCommand(cmd.context, removeCommand, interval, random, skipError) + return nil } diff --git a/pkg/chaos/docker/cmd/restart.go b/pkg/chaos/docker/cmd/restart.go index a4919490..5e97dcdb 100644 --- a/pkg/chaos/docker/cmd/restart.go +++ b/pkg/chaos/docker/cmd/restart.go @@ -5,10 +5,10 @@ import ( "fmt" "time" - "github.com/urfave/cli" - "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/chaos/docker" + "github.com/pkg/errors" + "github.com/urfave/cli" ) type restartContext struct { @@ -21,15 +21,10 @@ func NewRestartCLICommand(ctx context.Context) *cli.Command { return &cli.Command{ Name: "restart", Flags: []cli.Flag{ - cli.IntFlag{ - Name: "timeout, s", - Usage: "restart timeout for target container(s)", - Value: 1000, - }, - cli.IntFlag{ - Name: "delay, d", - Usage: "restart delay for target container(s)", - Value: 1000, + cli.DurationFlag{ + Name: "timeout, t", + Usage: "time to wait before killing the container", + Value: 1 * time.Second, }, cli.IntFlag{ Name: "limit, l", @@ -46,29 +41,21 @@ func NewRestartCLICommand(ctx context.Context) *cli.Command { // RESTART Command func (cmd *restartContext) restart(c *cli.Context) error { - // get random - random := c.GlobalBool("random") - // get labels - labels := c.GlobalStringSlice("label") - // get dry-run mode - dryRun := c.GlobalBool("dry-run") - // get skip error flag - skipError := c.GlobalBool("skip-error") - // get interval - interval := c.GlobalString("interval") - // get names or pattern - names, pattern := chaos.GetNamesOrPattern(c) + // parse common chaos flags + params, err := chaos.ParseGlobalParams(c) + if err != nil { + return errors.Wrap(err, "error parsing global parameters") + } // get timeout - timeout := time.Duration(c.Int("timeout")) * time.Millisecond - // get delay - delay := time.Duration(c.Int("delay")) * time.Millisecond + timeout := c.Duration("timeout") // get limit for number of containers to restart limit := c.Int("limit") // init restart command - restartCommand, err := docker.NewRestartCommand(chaos.DockerClient, names, pattern, labels, timeout, delay, limit, dryRun) + restartCommand := docker.NewRestartCommand(chaos.DockerClient, params, timeout, limit) + // run restart command + err = chaos.RunChaosCommand(cmd.context, restartCommand, params) if err != nil { - return err + return errors.Wrap(err, "error running restart command") } - // run restart command - return chaos.RunChaosCommand(cmd.context, restartCommand, interval, random, skipError) + return nil } diff --git a/pkg/chaos/docker/cmd/stop.go b/pkg/chaos/docker/cmd/stop.go index b1a6339c..0eca77f8 100644 --- a/pkg/chaos/docker/cmd/stop.go +++ b/pkg/chaos/docker/cmd/stop.go @@ -4,10 +4,10 @@ import ( "context" "fmt" - "github.com/urfave/cli" - "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/chaos/docker" + "github.com/pkg/errors" + "github.com/urfave/cli" ) type stopContext struct { @@ -49,31 +49,28 @@ func NewStopCLICommand(ctx context.Context) *cli.Command { // STOP Command func (cmd *stopContext) stop(c *cli.Context) error { - // get random flag - random := c.GlobalBool("random") - // get labels - labels := c.GlobalStringSlice("label") - // get dry-run mode - dryRun := c.GlobalBool("dry-run") - // get skip error flag - skipError := c.GlobalBool("skip-error") - // get global chaos interval - interval := c.GlobalString("interval") + // parse common chaos flags + params, err := chaos.ParseGlobalParams(c) + if err != nil { + return errors.Wrap(err, "error parsing global parameters") + } // get wait time waitTime := c.Int("time") // get limit for number of containers to kill limit := c.Int("limit") - // get names or pattern - names, pattern := chaos.GetNamesOrPattern(c) // get restart flag restart := c.Bool("restart") // get chaos command duration - duration := c.String("duration") + duration := c.Duration("duration") + if duration == 0 { + return errors.New("unset or invalid duration value") + } // init stop command - stopCommand, err := docker.NewStopCommand(chaos.DockerClient, names, pattern, labels, restart, interval, duration, waitTime, limit, dryRun) + stopCommand := docker.NewStopCommand(chaos.DockerClient, params, restart, duration, waitTime, limit) + // run stop command + err = chaos.RunChaosCommand(cmd.context, stopCommand, params) if err != nil { - return err + return errors.Wrap(err, "failed to stop containers") } - // run stop command - return chaos.RunChaosCommand(cmd.context, stopCommand, interval, random, skipError) + return nil } diff --git a/pkg/chaos/docker/exec.go b/pkg/chaos/docker/exec.go index da30d79e..550ae6b5 100644 --- a/pkg/chaos/docker/exec.go +++ b/pkg/chaos/docker/exec.go @@ -9,8 +9,8 @@ import ( log "github.com/sirupsen/logrus" ) -// ExecCommand `docker exec` command -type ExecCommand struct { +// `docker exec` command +type execCommand struct { client container.Client names []string pattern string @@ -21,16 +21,24 @@ type ExecCommand struct { } // NewExecCommand create new Exec Command instance -func NewExecCommand(client container.Client, names []string, pattern string, labels []string, command string, limit int, dryRun bool) (chaos.Command, error) { - exec := &ExecCommand{client, names, pattern, labels, command, limit, dryRun} +func NewExecCommand(client container.Client, params *chaos.GlobalParams, command string, limit int) chaos.Command { + exec := &execCommand{ + client: client, + names: params.Names, + pattern: params.Pattern, + labels: params.Labels, + command: command, + limit: limit, + dryRun: params.DryRun, + } if exec.command == "" { exec.command = "kill 1" } - return exec, nil + return exec } // Run exec command -func (k *ExecCommand) Run(ctx context.Context, random bool) error { +func (k *execCommand) Run(ctx context.Context, random bool) error { log.Debug("execing all matching containers") log.WithFields(log.Fields{ "names": k.names, @@ -41,29 +49,28 @@ func (k *ExecCommand) Run(ctx context.Context, random bool) error { }).Debug("listing matching containers") containers, err := container.ListNContainers(ctx, k.client, k.names, k.pattern, k.labels, k.limit) if err != nil { - return err + return errors.Wrap(err, "error listing containers") } if len(containers) == 0 { log.Warning("no containers to exec") return nil } - // select single random container from matching container and replace list with selected item + // select single random c from matching c and replace list with selected item if random { if c := container.RandomContainer(containers); c != nil { containers = []*container.Container{c} } } - - for _, container := range containers { + for _, c := range containers { log.WithFields(log.Fields{ - "container": container, - "command": k.command, - }).Debug("execing container") - c := container - err = k.client.ExecContainer(ctx, c, k.command, k.dryRun) + "c": *c, + "command": k.command, + }).Debug("execing c") + cc := c + err = k.client.ExecContainer(ctx, cc, k.command, k.dryRun) if err != nil { - return errors.Wrap(err, "failed to exec container") + return errors.Wrap(err, "failed to run exec command") } } return nil diff --git a/pkg/chaos/docker/exec_test.go b/pkg/chaos/docker/exec_test.go index 3f44f748..d0e6316c 100644 --- a/pkg/chaos/docker/exec_test.go +++ b/pkg/chaos/docker/exec_test.go @@ -3,7 +3,6 @@ package docker import ( "context" "errors" - "reflect" "testing" "github.com/alexei-led/pumba/pkg/chaos" @@ -11,19 +10,15 @@ import ( "github.com/stretchr/testify/mock" ) -//nolint:funlen func TestExecCommand_Run(t *testing.T) { type wantErrors struct { listError bool execError bool } type fields struct { - names []string - pattern string - labels []string + params *chaos.GlobalParams command string limit int - dryRun bool } type args struct { ctx context.Context @@ -40,7 +35,9 @@ func TestExecCommand_Run(t *testing.T) { { name: "exec matching containers by names", fields: fields{ - names: []string{"c1", "c2", "c3"}, + params: &chaos.GlobalParams{ + Names: []string{"c1", "c2"}, + }, command: "kill 1", }, args: args{ @@ -51,8 +48,10 @@ func TestExecCommand_Run(t *testing.T) { { name: "exec matching labeled containers by names", fields: fields{ - names: []string{"c1", "c2", "c3"}, - labels: []string{"key=value"}, + params: &chaos.GlobalParams{ + Names: []string{"c1", "c2", "c3"}, + Labels: []string{"key=value"}, + }, command: "kill 1", }, args: args{ @@ -63,7 +62,9 @@ func TestExecCommand_Run(t *testing.T) { { name: "exec matching containers by filter with limit", fields: fields{ - pattern: "^c?", + params: &chaos.GlobalParams{ + Pattern: "^c?", + }, command: "kill -STOP 1", limit: 2, }, @@ -75,7 +76,9 @@ func TestExecCommand_Run(t *testing.T) { { name: "exec random matching container by names", fields: fields{ - names: []string{"c1", "c2", "c3"}, + params: &chaos.GlobalParams{ + Names: []string{"c1", "c2", "c3"}, + }, command: "kill 1", }, args: args{ @@ -87,7 +90,9 @@ func TestExecCommand_Run(t *testing.T) { { name: "no matching containers by names", fields: fields{ - names: []string{"c1", "c2", "c3"}, + params: &chaos.GlobalParams{ + Names: []string{"c1", "c2", "c3"}, + }, command: "kill 1", }, args: args{ @@ -97,7 +102,9 @@ func TestExecCommand_Run(t *testing.T) { { name: "error listing containers", fields: fields{ - names: []string{"c1", "c2", "c3"}, + params: &chaos.GlobalParams{ + Names: []string{"c1", "c2", "c3"}, + }, command: "kill 1", }, args: args{ @@ -109,7 +116,9 @@ func TestExecCommand_Run(t *testing.T) { { name: "error execing container", fields: fields{ - names: []string{"c1", "c2", "c3"}, + params: &chaos.GlobalParams{ + Names: []string{"c1", "c2", "c3"}, + }, command: "kill 1", }, args: args{ @@ -123,16 +132,16 @@ func TestExecCommand_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockClient := new(container.MockClient) - k := &ExecCommand{ + k := &execCommand{ client: mockClient, - names: tt.fields.names, - pattern: tt.fields.pattern, - labels: tt.fields.labels, + names: tt.fields.params.Names, + pattern: tt.fields.params.Pattern, + labels: tt.fields.params.Labels, command: tt.fields.command, limit: tt.fields.limit, - dryRun: tt.fields.dryRun, + dryRun: tt.fields.params.DryRun, } - opts := container.ListOpts{Labels: tt.fields.labels} + opts := container.ListOpts{Labels: tt.fields.params.Labels} call := mockClient.On("ListContainers", tt.args.ctx, mock.AnythingOfType("container.FilterFunc"), opts) if tt.errs.listError { call.Return(tt.expected, errors.New("ERROR")) @@ -144,11 +153,11 @@ func TestExecCommand_Run(t *testing.T) { } } if tt.args.random { - mockClient.On("ExecContainer", tt.args.ctx, mock.AnythingOfType("*container.Container"), tt.fields.command, tt.fields.dryRun).Return(nil) + mockClient.On("ExecContainer", tt.args.ctx, mock.AnythingOfType("*container.Container"), tt.fields.command, tt.fields.params.DryRun).Return(nil) } else { for i := range tt.expected { if tt.fields.limit == 0 || i < tt.fields.limit { - call = mockClient.On("ExecContainer", tt.args.ctx, mock.AnythingOfType("*container.Container"), tt.fields.command, tt.fields.dryRun) + call = mockClient.On("ExecContainer", tt.args.ctx, mock.AnythingOfType("*container.Container"), tt.fields.command, tt.fields.params.DryRun) if tt.errs.execError { call.Return(errors.New("ERROR")) goto Invoke @@ -166,58 +175,3 @@ func TestExecCommand_Run(t *testing.T) { }) } } - -func TestNewExecCommand(t *testing.T) { - type args struct { - client container.Client - names []string - pattern string - labels []string - command string - limit int - dryRun bool - } - tests := []struct { - name string - args args - want chaos.Command - wantErr bool - }{ - { - name: "create new exec command", - args: args{ - names: []string{"c1", "c2"}, - command: "kill -TERM 1", - limit: 10, - }, - want: &ExecCommand{ - names: []string{"c1", "c2"}, - command: "kill -TERM 1", - limit: 10, - }, - }, - { - name: "empty command", - args: args{ - names: []string{"c1", "c2"}, - command: "", - }, - want: &ExecCommand{ - names: []string{"c1", "c2"}, - command: "kill 1", - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := NewExecCommand(tt.args.client, tt.args.names, tt.args.pattern, tt.args.labels, tt.args.command, tt.args.limit, tt.args.dryRun) - if (err != nil) != tt.wantErr { - t.Errorf("NewExecCommand() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewExecCommand() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/chaos/docker/kill.go b/pkg/chaos/docker/kill.go index 025d8bb3..9bcb84ec 100644 --- a/pkg/chaos/docker/kill.go +++ b/pkg/chaos/docker/kill.go @@ -2,6 +2,7 @@ package docker import ( "context" + "syscall" "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/container" @@ -14,43 +15,43 @@ const ( DefaultKillSignal = "SIGKILL" ) -// LinuxSignals valid Linux signal table +// valid Linux signal table // http://www.comptechdoc.org/os/linux/programming/linux_pgsignals.html -var LinuxSignals = map[string]int{ - "SIGHUP": 1, - "SIGINT": 2, - "SIGQUIT": 3, - "SIGILL": 4, - "SIGTRAP": 5, - "SIGIOT": 6, - "SIGBUS": 7, - "SIGFPE": 8, - "SIGKILL": 9, - "SIGUSR1": 10, - "SIGSEGV": 11, - "SIGUSR2": 12, - "SIGPIPE": 13, - "SIGALRM": 14, - "SIGTERM": 15, - "SIGSTKFLT": 16, - "SIGCHLD": 17, - "SIGCONT": 18, - "SIGSTOP": 19, - "SIGTSTP": 20, - "SIGTTIN": 21, - "SIGTTOU": 22, - "SIGURG": 23, - "SIGXCPU": 24, - "SIGXFSZ": 25, - "SIGVTALRM": 26, - "SIGPROF": 27, - "SIGWINCH": 28, - "SIGIO": 29, - "SIGPWR": 30, +var linuxSignals = map[string]syscall.Signal{ + "SIGHUP": syscall.SIGHUP, + "SIGINT": syscall.SIGINT, + "SIGQUIT": syscall.SIGQUIT, + "SIGILL": syscall.SIGILL, + "SIGTRAP": syscall.SIGTRAP, + "SIGIOT": syscall.SIGIOT, + "SIGBUS": syscall.SIGBUS, + "SIGFPE": syscall.SIGFPE, + "SIGKILL": syscall.SIGKILL, + "SIGUSR1": syscall.SIGUSR1, + "SIGSEGV": syscall.SIGSEGV, + "SIGUSR2": syscall.SIGUSR2, + "SIGPIPE": syscall.SIGPIPE, + "SIGALRM": syscall.SIGALRM, + "SIGTERM": syscall.SIGTERM, + "SIGSTKFLT": 16, //nolint:gomnd + "SIGCHLD": syscall.SIGCHLD, + "SIGCONT": syscall.SIGCONT, + "SIGSTOP": syscall.SIGSTOP, + "SIGTSTP": syscall.SIGTSTP, + "SIGTTIN": syscall.SIGTTIN, + "SIGTTOU": syscall.SIGTTOU, + "SIGURG": syscall.SIGURG, + "SIGXCPU": syscall.SIGXCPU, + "SIGXFSZ": syscall.SIGXFSZ, + "SIGVTALRM": syscall.SIGVTALRM, + "SIGPROF": syscall.SIGPROF, + "SIGWINCH": syscall.SIGWINCH, + "SIGIO": syscall.SIGIO, + "SIGPWR": 30, //nolint:gomnd } -// KillCommand `docker kill` command -type KillCommand struct { +// `docker kill` command +type killCommand struct { client container.Client names []string pattern string @@ -61,19 +62,27 @@ type KillCommand struct { } // NewKillCommand create new Kill Command instance -func NewKillCommand(client container.Client, names []string, pattern string, labels []string, signal string, limit int, dryRun bool) (chaos.Command, error) { - kill := &KillCommand{client, names, pattern, labels, signal, limit, dryRun} +func NewKillCommand(client container.Client, params *chaos.GlobalParams, signal string, limit int) (chaos.Command, error) { + kill := &killCommand{ + client: client, + names: params.Names, + pattern: params.Pattern, + labels: params.Labels, + signal: signal, + limit: limit, + dryRun: params.DryRun, + } if kill.signal == "" { kill.signal = DefaultKillSignal } - if _, ok := LinuxSignals[kill.signal]; !ok { + if _, ok := linuxSignals[kill.signal]; !ok { return nil, errors.Errorf("undefined Linux signal: %s", signal) } return kill, nil } // Run kill command -func (k *KillCommand) Run(ctx context.Context, random bool) error { +func (k *killCommand) Run(ctx context.Context, random bool) error { log.Debug("killing all matching containers") log.WithFields(log.Fields{ "names": k.names, @@ -84,7 +93,7 @@ func (k *KillCommand) Run(ctx context.Context, random bool) error { }).Debug("listing matching containers") containers, err := container.ListNContainers(ctx, k.client, k.names, k.pattern, k.labels, k.limit) if err != nil { - return err + return errors.Wrap(err, "error listing containers") } if len(containers) == 0 { log.Warning("no containers to kill") diff --git a/pkg/chaos/docker/kill_test.go b/pkg/chaos/docker/kill_test.go index 2ec13af2..9d8fb4e2 100644 --- a/pkg/chaos/docker/kill_test.go +++ b/pkg/chaos/docker/kill_test.go @@ -11,7 +11,6 @@ import ( "github.com/stretchr/testify/mock" ) -//nolint:funlen func TestKillCommand_Run(t *testing.T) { type wantErrors struct { listError bool @@ -123,7 +122,7 @@ func TestKillCommand_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockClient := new(container.MockClient) - k := &KillCommand{ + k := &killCommand{ client: mockClient, names: tt.fields.names, pattern: tt.fields.pattern, @@ -160,7 +159,7 @@ func TestKillCommand_Run(t *testing.T) { } Invoke: if err := k.Run(tt.args.ctx, tt.args.random); (err != nil) != tt.wantErr { - t.Errorf("KillCommand.Run() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("killCommand.Run() error = %v, wantErr %v", err, tt.wantErr) } mockClient.AssertExpectations(t) }) @@ -169,13 +168,10 @@ func TestKillCommand_Run(t *testing.T) { func TestNewKillCommand(t *testing.T) { type args struct { - client container.Client - names []string - pattern string - labels []string - signal string - limit int - dryRun bool + client container.Client + params *chaos.GlobalParams + signal string + limit int } tests := []struct { name string @@ -186,11 +182,11 @@ func TestNewKillCommand(t *testing.T) { { name: "create new kill command", args: args{ - names: []string{"c1", "c2"}, + params: &chaos.GlobalParams{Names: []string{"c1", "c2"}}, signal: "SIGTERM", limit: 10, }, - want: &KillCommand{ + want: &killCommand{ names: []string{"c1", "c2"}, signal: "SIGTERM", limit: 10, @@ -199,7 +195,7 @@ func TestNewKillCommand(t *testing.T) { { name: "invalid signal", args: args{ - names: []string{"c1", "c2"}, + params: &chaos.GlobalParams{Names: []string{"c1", "c2"}}, signal: "SIGNONE", }, wantErr: true, @@ -207,10 +203,10 @@ func TestNewKillCommand(t *testing.T) { { name: "empty signal", args: args{ - names: []string{"c1", "c2"}, + params: &chaos.GlobalParams{Names: []string{"c1", "c2"}}, signal: "", }, - want: &KillCommand{ + want: &killCommand{ names: []string{"c1", "c2"}, signal: DefaultKillSignal, }, @@ -218,7 +214,7 @@ func TestNewKillCommand(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := NewKillCommand(tt.args.client, tt.args.names, tt.args.pattern, tt.args.labels, tt.args.signal, tt.args.limit, tt.args.dryRun) + got, err := NewKillCommand(tt.args.client, tt.args.params, tt.args.signal, tt.args.limit) if (err != nil) != tt.wantErr { t.Errorf("NewKillCommand() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/pkg/chaos/docker/pause.go b/pkg/chaos/docker/pause.go index e828a847..996b0199 100644 --- a/pkg/chaos/docker/pause.go +++ b/pkg/chaos/docker/pause.go @@ -6,13 +6,12 @@ import ( "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/container" - "github.com/alexei-led/pumba/pkg/util" "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) -// PauseCommand `docker pause` command -type PauseCommand struct { +// `docker pause` command +type pauseCommand struct { client container.Client names []string pattern string @@ -23,22 +22,19 @@ type PauseCommand struct { } // NewPauseCommand create new Pause Command instance -func NewPauseCommand(client container.Client, names []string, pattern string, labels []string, intervalStr, durationStr string, limit int, dryRun bool) (chaos.Command, error) { - // get interval - interval, err := util.GetIntervalValue(intervalStr) - if err != nil { - return nil, err - } - // get duration - duration, err := util.GetDurationValue(durationStr, interval) - if err != nil { - return nil, err - } - return &PauseCommand{client, names, pattern, labels, duration, limit, dryRun}, nil +func NewPauseCommand(client container.Client, params *chaos.GlobalParams, duration time.Duration, limit int) chaos.Command { + return &pauseCommand{ + client: client, + names: params.Names, + pattern: params.Pattern, + labels: params.Labels, + duration: duration, + limit: limit, + dryRun: params.DryRun} } // Run pause command -func (p *PauseCommand) Run(ctx context.Context, random bool) error { +func (p *pauseCommand) Run(ctx context.Context, random bool) error { log.Debug("pausing all matching containers") log.WithFields(log.Fields{ "names": p.names, @@ -50,7 +46,7 @@ func (p *PauseCommand) Run(ctx context.Context, random bool) error { }).Debug("listing matching containers") containers, err := container.ListNContainers(ctx, p.client, p.names, p.pattern, p.labels, p.limit) if err != nil { - return err + return errors.Wrap(err, "error listing containers") } if len(containers) == 0 { log.Warning("no containers to stop") @@ -98,7 +94,7 @@ func (p *PauseCommand) Run(ctx context.Context, random bool) error { } // unpause containers -func (p *PauseCommand) unpauseContainers(ctx context.Context, containers []*container.Container) error { +func (p *pauseCommand) unpauseContainers(ctx context.Context, containers []*container.Container) error { var err error for _, container := range containers { log.WithField("container", container).Debug("unpause container") diff --git a/pkg/chaos/docker/pause_test.go b/pkg/chaos/docker/pause_test.go index cb2f2b73..57ecad99 100644 --- a/pkg/chaos/docker/pause_test.go +++ b/pkg/chaos/docker/pause_test.go @@ -3,64 +3,12 @@ package docker import ( "context" "errors" - "reflect" "testing" - "time" - "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/container" "github.com/stretchr/testify/mock" ) -func TestNewPauseCommand(t *testing.T) { - type args struct { - client container.Client - names []string - pattern string - labels []string - interval string - duration string - limit int - dryRun bool - } - tests := []struct { - name string - args args - want chaos.Command - wantErr bool - }{ - { - name: "new pause command", - args: args{ - names: []string{"c1", "c2"}, - pattern: "pattern", - interval: "20s", - duration: "10s", - limit: 15, - }, - want: &PauseCommand{ - names: []string{"c1", "c2"}, - pattern: "pattern", - duration: 10 * time.Second, - limit: 15, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := NewPauseCommand(tt.args.client, tt.args.names, tt.args.pattern, tt.args.labels, tt.args.interval, tt.args.duration, tt.args.limit, tt.args.dryRun) - if (err != nil) != tt.wantErr { - t.Errorf("NewPauseCommand() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewPauseCommand() = %v, want %v", got, tt.want) - } - }) - } -} - -//nolint:funlen func TestPauseCommand_Run(t *testing.T) { type wantErrors struct { listError bool @@ -165,7 +113,7 @@ func TestPauseCommand_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockClient := new(container.MockClient) - s := &PauseCommand{ + s := &pauseCommand{ client: mockClient, names: tt.fields.names, pattern: tt.fields.pattern, diff --git a/pkg/chaos/docker/remove.go b/pkg/chaos/docker/remove.go index 8d631e5f..001d3466 100644 --- a/pkg/chaos/docker/remove.go +++ b/pkg/chaos/docker/remove.go @@ -5,11 +5,12 @@ import ( "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/container" + "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) -// RemoveCommand `docker kill` command -type RemoveCommand struct { +// `docker kill` command +type removeCommand struct { client container.Client names []string pattern string @@ -22,13 +23,23 @@ type RemoveCommand struct { } // NewRemoveCommand create new Kill Command instance -func NewRemoveCommand(client container.Client, names []string, pattern string, labels []string, force, links, volumes bool, limit int, dryRun bool) (chaos.Command, error) { - remove := &RemoveCommand{client, names, pattern, labels, force, links, volumes, limit, dryRun} - return remove, nil +func NewRemoveCommand(client container.Client, params *chaos.GlobalParams, force, links, volumes bool, limit int) chaos.Command { + remove := &removeCommand{ + client: client, + names: params.Names, + pattern: params.Pattern, + labels: params.Labels, + force: force, + links: links, + volumes: volumes, + limit: limit, + dryRun: params.DryRun, + } + return remove } // Run remove command -func (r *RemoveCommand) Run(ctx context.Context, random bool) error { +func (r *removeCommand) Run(ctx context.Context, random bool) error { log.Debug("removing all matching containers") log.WithFields(log.Fields{ "names": r.names, @@ -39,7 +50,7 @@ func (r *RemoveCommand) Run(ctx context.Context, random bool) error { }).Debug("listing matching containers") containers, err := container.ListNContainers(ctx, r.client, r.names, r.pattern, r.labels, r.limit) if err != nil { - return err + return errors.Wrap(err, "error listing containers") } if len(containers) == 0 { log.Warning("no containers to remove") @@ -63,7 +74,7 @@ func (r *RemoveCommand) Run(ctx context.Context, random bool) error { c := container err = r.client.RemoveContainer(ctx, c, r.force, r.links, r.volumes, r.dryRun) if err != nil { - return err + return errors.Wrap(err, "failed to remove container") } } return nil diff --git a/pkg/chaos/docker/remove_test.go b/pkg/chaos/docker/remove_test.go index 76f28c4a..54712e26 100644 --- a/pkg/chaos/docker/remove_test.go +++ b/pkg/chaos/docker/remove_test.go @@ -3,15 +3,12 @@ package docker import ( "context" "errors" - "reflect" "testing" - "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/container" "github.com/stretchr/testify/mock" ) -//nolint:funlen func TestRemoveCommand_Run(t *testing.T) { type wantErrors struct { listError bool @@ -112,7 +109,7 @@ func TestRemoveCommand_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockClient := new(container.MockClient) - k := &RemoveCommand{ + k := &removeCommand{ client: mockClient, names: tt.fields.names, pattern: tt.fields.pattern, @@ -149,59 +146,9 @@ func TestRemoveCommand_Run(t *testing.T) { } Invoke: if err := k.Run(tt.args.ctx, tt.args.random); (err != nil) != tt.wantErr { - t.Errorf("RemoveCommand.Run() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("removeCommand.Run() error = %v, wantErr %v", err, tt.wantErr) } mockClient.AssertExpectations(t) }) } } - -func TestNewRemoveCommand(t *testing.T) { - type args struct { - client container.Client - names []string - pattern string - labels []string - force bool - links bool - volumes bool - limit int - dryRun bool - } - tests := []struct { - name string - args args - want chaos.Command - wantErr bool - }{ - { - name: "create new remove command", - args: args{ - names: []string{"c1", "c2"}, - force: true, - links: true, - volumes: false, - limit: 10, - }, - want: &RemoveCommand{ - names: []string{"c1", "c2"}, - force: true, - links: true, - volumes: false, - limit: 10, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := NewRemoveCommand(tt.args.client, tt.args.names, tt.args.pattern, tt.args.labels, tt.args.force, tt.args.links, tt.args.volumes, tt.args.limit, tt.args.dryRun) - if (err != nil) != tt.wantErr { - t.Errorf("NewRemoveCommand() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewRemoveCommand() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/chaos/docker/restart.go b/pkg/chaos/docker/restart.go index c3e83d13..e7aa2838 100644 --- a/pkg/chaos/docker/restart.go +++ b/pkg/chaos/docker/restart.go @@ -10,26 +10,32 @@ import ( log "github.com/sirupsen/logrus" ) -// RestartCommand `docker restart` command -type RestartCommand struct { +// `docker restart` command +type restartCommand struct { client container.Client names []string pattern string labels []string timeout time.Duration - delay time.Duration limit int dryRun bool } // NewRestartCommand create new Restart Command instance -func NewRestartCommand(client container.Client, names []string, pattern string, labels []string, timeout time.Duration, delay time.Duration, limit int, dryRun bool) (chaos.Command, error) { - restart := &RestartCommand{client, names, pattern, labels, timeout, delay, limit, dryRun} - return restart, nil +func NewRestartCommand(client container.Client, params *chaos.GlobalParams, timeout time.Duration, limit int) chaos.Command { + return &restartCommand{ + client: client, + names: params.Names, + pattern: params.Pattern, + labels: params.Labels, + timeout: timeout, + limit: limit, + dryRun: params.DryRun, + } } // Run restart command -func (k *RestartCommand) Run(ctx context.Context, random bool) error { +func (k *restartCommand) Run(ctx context.Context, random bool) error { log.Debug("restarting all matching containers") log.WithFields(log.Fields{ "names": k.names, @@ -40,7 +46,7 @@ func (k *RestartCommand) Run(ctx context.Context, random bool) error { }).Debug("listing matching containers") containers, err := container.ListNContainers(ctx, k.client, k.names, k.pattern, k.labels, k.limit) if err != nil { - return err + return errors.Wrap(err, "error listing containers") } if len(containers) == 0 { log.Warning("no containers to restart") @@ -54,13 +60,12 @@ func (k *RestartCommand) Run(ctx context.Context, random bool) error { } } - for _, container := range containers { + for _, c := range containers { log.WithFields(log.Fields{ - "container": container, + "container": c, "timeout": k.timeout, }).Debug("restarting container") - c := container - err = k.client.RestartContainer(ctx, c, k.timeout, k.delay, k.dryRun) + err = k.client.RestartContainer(ctx, c, k.timeout, k.dryRun) if err != nil { return errors.Wrap(err, "failed to restart container") } diff --git a/pkg/chaos/docker/restart_test.go b/pkg/chaos/docker/restart_test.go index 9d1c8be8..87a47aa5 100644 --- a/pkg/chaos/docker/restart_test.go +++ b/pkg/chaos/docker/restart_test.go @@ -3,16 +3,13 @@ package docker import ( "context" "errors" - "reflect" "testing" "time" - "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/container" "github.com/stretchr/testify/mock" ) -//nolint:funlen func TestRestartCommand_Run(t *testing.T) { type wantErrors struct { listError bool @@ -23,7 +20,6 @@ func TestRestartCommand_Run(t *testing.T) { pattern string labels []string timeout time.Duration - delay time.Duration limit int dryRun bool } @@ -44,7 +40,6 @@ func TestRestartCommand_Run(t *testing.T) { fields: fields{ names: []string{"c1", "c2", "c3"}, timeout: 1 * time.Second, - delay: 1 * time.Second, }, args: args{ ctx: context.TODO(), @@ -57,7 +52,6 @@ func TestRestartCommand_Run(t *testing.T) { names: []string{"c1", "c2", "c3"}, labels: []string{"key=value"}, timeout: 1 * time.Second, - delay: 1 * time.Second, }, args: args{ ctx: context.TODO(), @@ -69,7 +63,6 @@ func TestRestartCommand_Run(t *testing.T) { fields: fields{ pattern: "^c?", timeout: 1 * time.Second, - delay: 1 * time.Second, limit: 2, }, args: args{ @@ -82,7 +75,6 @@ func TestRestartCommand_Run(t *testing.T) { fields: fields{ names: []string{"c1", "c2", "c3"}, timeout: 1 * time.Second, - delay: 1 * time.Second, }, args: args{ ctx: context.TODO(), @@ -95,7 +87,6 @@ func TestRestartCommand_Run(t *testing.T) { fields: fields{ names: []string{"c1", "c2", "c3"}, timeout: 1 * time.Second, - delay: 1 * time.Second, }, args: args{ ctx: context.TODO(), @@ -106,7 +97,6 @@ func TestRestartCommand_Run(t *testing.T) { fields: fields{ names: []string{"c1", "c2", "c3"}, timeout: 1 * time.Second, - delay: 1 * time.Second, }, args: args{ ctx: context.TODO(), @@ -119,7 +109,6 @@ func TestRestartCommand_Run(t *testing.T) { fields: fields{ names: []string{"c1", "c2", "c3"}, timeout: 1 * time.Second, - delay: 1 * time.Second, }, args: args{ ctx: context.TODO(), @@ -132,13 +121,12 @@ func TestRestartCommand_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockClient := new(container.MockClient) - k := &RestartCommand{ + k := &restartCommand{ client: mockClient, names: tt.fields.names, pattern: tt.fields.pattern, labels: tt.fields.labels, timeout: 1 * time.Second, - delay: 1 * time.Second, limit: tt.fields.limit, dryRun: tt.fields.dryRun, } @@ -154,11 +142,11 @@ func TestRestartCommand_Run(t *testing.T) { } } if tt.args.random { - mockClient.On("RestartContainer", tt.args.ctx, mock.AnythingOfType("*container.Container"), tt.fields.timeout, tt.fields.delay, tt.fields.dryRun).Return(nil) + mockClient.On("RestartContainer", tt.args.ctx, mock.AnythingOfType("*container.Container"), tt.fields.timeout, tt.fields.dryRun).Return(nil) } else { for i := range tt.expected { if tt.fields.limit == 0 || i < tt.fields.limit { - call = mockClient.On("RestartContainer", tt.args.ctx, mock.AnythingOfType("*container.Container"), tt.fields.timeout, tt.fields.delay, tt.fields.dryRun) + call = mockClient.On("RestartContainer", tt.args.ctx, mock.AnythingOfType("*container.Container"), tt.fields.timeout, tt.fields.dryRun) if tt.errs.restartError { call.Return(errors.New("ERROR")) goto Invoke @@ -170,69 +158,9 @@ func TestRestartCommand_Run(t *testing.T) { } Invoke: if err := k.Run(tt.args.ctx, tt.args.random); (err != nil) != tt.wantErr { - t.Errorf("RestartCommand.Run() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("restartCommand.Run() error = %v, wantErr %v", err, tt.wantErr) } mockClient.AssertExpectations(t) }) } } - -func TestNewRestartCommand(t *testing.T) { - type args struct { - client container.Client - names []string - pattern string - labels []string - timeout time.Duration - delay time.Duration - limit int - dryRun bool - } - tests := []struct { - name string - args args - want chaos.Command - wantErr bool - }{ - { - name: "create new restart command", - args: args{ - names: []string{"c1", "c2"}, - timeout: 1 * time.Second, - delay: 1 * time.Second, - limit: 10, - }, - want: &RestartCommand{ - names: []string{"c1", "c2"}, - timeout: 1 * time.Second, - delay: 1 * time.Second, - limit: 10, - }, - }, - { - name: "empty command", - args: args{ - names: []string{"c1", "c2"}, - timeout: 1 * time.Second, - delay: 1 * time.Second, - }, - want: &RestartCommand{ - names: []string{"c1", "c2"}, - timeout: 1 * time.Second, - delay: 1 * time.Second, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := NewRestartCommand(tt.args.client, tt.args.names, tt.args.pattern, tt.args.labels, tt.args.timeout, tt.args.delay, tt.args.limit, tt.args.dryRun) - if (err != nil) != tt.wantErr { - t.Errorf("NewRestartCommand() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewRestartCommand() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/chaos/docker/stop.go b/pkg/chaos/docker/stop.go index a7cdb457..9828f9be 100644 --- a/pkg/chaos/docker/stop.go +++ b/pkg/chaos/docker/stop.go @@ -6,7 +6,6 @@ import ( "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/container" - "github.com/alexei-led/pumba/pkg/util" "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) @@ -16,8 +15,8 @@ const ( DeafultWaitTime = 5 ) -// StopCommand `docker stop` command -type StopCommand struct { +// `docker stop` command +type stopCommand struct { client container.Client names []string pattern string @@ -30,25 +29,24 @@ type StopCommand struct { } // NewStopCommand create new Stop Command instance -func NewStopCommand(client container.Client, names []string, pattern string, labels []string, restart bool, intervalStr, durationStr string, waitTime, limit int, dryRun bool) (chaos.Command, error) { +func NewStopCommand(client container.Client, params *chaos.GlobalParams, restart bool, duration time.Duration, waitTime, limit int) chaos.Command { if waitTime <= 0 { waitTime = DeafultWaitTime } - // get interval - interval, err := util.GetIntervalValue(intervalStr) - if err != nil { - return nil, err - } - // get duration - duration, err := util.GetDurationValue(durationStr, interval) - if err != nil { - return nil, err - } - return &StopCommand{client, names, pattern, labels, restart, duration, waitTime, limit, dryRun}, nil + return &stopCommand{ + client: client, + names: params.Names, + pattern: params.Pattern, + labels: params.Labels, + dryRun: params.DryRun, + restart: restart, + duration: duration, + waitTime: waitTime, + limit: limit} } // Run stop command -func (s *StopCommand) Run(ctx context.Context, random bool) error { +func (s *stopCommand) Run(ctx context.Context, random bool) error { log.Debug("stopping all matching containers") log.WithFields(log.Fields{ "names": s.names, @@ -61,7 +59,7 @@ func (s *StopCommand) Run(ctx context.Context, random bool) error { }).Debug("listing matching containers") containers, err := container.ListNContainers(ctx, s.client, s.names, s.pattern, s.labels, s.limit) if err != nil { - return err + return errors.Wrap(err, "error listing containers") } if len(containers) == 0 { log.Warning("no containers to stop") @@ -109,7 +107,7 @@ func (s *StopCommand) Run(ctx context.Context, random bool) error { } // start previously stopped containers after duration on exit -func (s *StopCommand) startStoppedContainers(ctx context.Context, containers []*container.Container) error { +func (s *stopCommand) startStoppedContainers(ctx context.Context, containers []*container.Container) error { var err error for _, container := range containers { c := container diff --git a/pkg/chaos/docker/stop_test.go b/pkg/chaos/docker/stop_test.go index cc7f6f3f..f0390f5c 100644 --- a/pkg/chaos/docker/stop_test.go +++ b/pkg/chaos/docker/stop_test.go @@ -3,87 +3,12 @@ package docker import ( "context" "errors" - "reflect" "testing" - "time" - "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/container" "github.com/stretchr/testify/mock" ) -func TestNewStopCommand(t *testing.T) { - type args struct { - client container.Client - names []string - pattern string - labels []string - restart bool - interval string - duration string - waitTime int - limit int - dryRun bool - } - tests := []struct { - name string - args args - want chaos.Command - wantErr bool - }{ - { - name: "new stop command", - args: args{ - names: []string{"c1", "c2"}, - pattern: "pattern", - restart: true, - interval: "20s", - duration: "10s", - waitTime: 100, - limit: 15, - }, - want: &StopCommand{ - names: []string{"c1", "c2"}, - pattern: "pattern", - restart: true, - duration: 10 * time.Second, - waitTime: 100, - limit: 15, - }, - }, - { - name: "new stop command with default wait", - args: args{ - names: []string{"c1", "c2"}, - pattern: "pattern", - duration: "10s", - waitTime: 0, - limit: 15, - }, - want: &StopCommand{ - names: []string{"c1", "c2"}, - pattern: "pattern", - duration: 10 * time.Second, - waitTime: DeafultWaitTime, - limit: 15, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := NewStopCommand(tt.args.client, tt.args.names, tt.args.pattern, tt.args.labels, tt.args.restart, tt.args.interval, tt.args.duration, tt.args.waitTime, tt.args.limit, tt.args.dryRun) - if (err != nil) != tt.wantErr { - t.Errorf("NewStopCommand() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewStopCommand() = %v, want %v", got, tt.want) - } - }) - } -} - -//nolint:funlen func TestStopCommand_Run(t *testing.T) { type wantErrors struct { listError bool @@ -223,7 +148,7 @@ func TestStopCommand_Run(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockClient := new(container.MockClient) - s := &StopCommand{ + s := &stopCommand{ client: mockClient, names: tt.fields.names, pattern: tt.fields.pattern, @@ -271,7 +196,7 @@ func TestStopCommand_Run(t *testing.T) { } Invoke: if err := s.Run(tt.args.ctx, tt.args.random); (err != nil) != tt.wantErr { - t.Errorf("StopCommand.Run() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("stopCommand.Run() error = %v, wantErr %v", err, tt.wantErr) } mockClient.AssertExpectations(t) }) diff --git a/pkg/chaos/netem/cmd/corrupt.go b/pkg/chaos/netem/cmd/corrupt.go index d6c70978..670dd448 100644 --- a/pkg/chaos/netem/cmd/corrupt.go +++ b/pkg/chaos/netem/cmd/corrupt.go @@ -4,10 +4,10 @@ import ( "context" "fmt" - "github.com/urfave/cli" - "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/chaos/netem" + "github.com/pkg/errors" + "github.com/urfave/cli" ) type corruptContext struct { @@ -40,46 +40,29 @@ func NewCorruptCLICommand(ctx context.Context) *cli.Command { // NETEM Corrupt Command - network emulation corrupt func (cmd *corruptContext) corrupt(c *cli.Context) error { - // get random flag - random := c.GlobalBool("random") - // get labels - labels := c.GlobalStringSlice("label") - // get dry-run mode - dryRun := c.GlobalBool("dry-run") - // get skip error flag - skipError := c.GlobalBool("skip-error") - // get names or pattern - names, pattern := chaos.GetNamesOrPattern(c) - // get global chaos interval - interval := c.GlobalString("interval") - - // get network interface from parent `netem` command - iface := c.Parent().String("interface") - // get ips list from parent `netem`` command `target` flag - ips := c.Parent().StringSlice("target") - // get egress port list from parent `netem` command `egressPort` flag - sports := c.Parent().String("egressPort") - // get ingress port list from parent `netem` command `ingressPort` flag - dports := c.Parent().String("ingressPort") - // get duration from parent `netem`` command - duration := c.Parent().String("duration") - // get traffic control image from parent `netem` command - image := c.Parent().String("tc-image") - // get pull tc image flag - pull := c.Parent().BoolT("pull-image") - // get limit for number of containers to netem - limit := c.Parent().Int("limit") - + // parse common chaos flags + globalParams, err := chaos.ParseGlobalParams(c) + if err != nil { + return errors.Wrap(err, "error parsing global parameters") + } + // parse netem flags + netemParams, err := parseNetemParams(c.Parent(), globalParams.Interval) + if err != nil { + return errors.Wrap(err, "error parsing netem parameters") + } // get corrupt percentage percent := c.Float64("percent") // get delay variation correlation := c.Float64("correlation") - // init netem corrupt command - corruptCommand, err := netem.NewCorruptCommand(chaos.DockerClient, names, pattern, labels, iface, ips, sports, dports, duration, interval, percent, correlation, image, pull, limit, dryRun) + corruptCommand, err := netem.NewCorruptCommand(chaos.DockerClient, globalParams, netemParams, percent, correlation) if err != nil { - return err + return errors.Wrap(err, "error creating netem corrupt command") } // run netem command - return chaos.RunChaosCommand(cmd.context, corruptCommand, interval, random, skipError) + err = chaos.RunChaosCommand(cmd.context, corruptCommand, globalParams) + if err != nil { + return errors.Wrap(err, "error running netem corrupt command") + } + return nil } diff --git a/pkg/chaos/netem/cmd/delay.go b/pkg/chaos/netem/cmd/delay.go index 5767c912..83c1cfd7 100644 --- a/pkg/chaos/netem/cmd/delay.go +++ b/pkg/chaos/netem/cmd/delay.go @@ -1,13 +1,14 @@ +//nolint:dupl package cmd import ( "context" "fmt" - "github.com/urfave/cli" - "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/chaos/netem" + "github.com/pkg/errors" + "github.com/urfave/cli" ) type delayContext struct { @@ -23,17 +24,17 @@ func NewDelayCLICommand(ctx context.Context) *cli.Command { cli.IntFlag{ Name: "time, t", Usage: "delay time; in milliseconds", - Value: 100, + Value: 100, //nolint:gomnd }, cli.IntFlag{ Name: "jitter, j", Usage: "random delay variation (jitter); in milliseconds; example: 100ms ± 10ms", - Value: 10, + Value: 10, //nolint:gomnd }, cli.Float64Flag{ Name: "correlation, c", Usage: "delay correlation; in percentage", - Value: 20, + Value: 20, //nolint:gomnd }, cli.StringFlag{ Name: "distribution, d", @@ -50,36 +51,16 @@ func NewDelayCLICommand(ctx context.Context) *cli.Command { // NETEM DELAY Command - network emulation delay func (cmd *delayContext) delay(c *cli.Context) error { - // get random flag - random := c.GlobalBool("random") - // get labels - labels := c.GlobalStringSlice("label") - // get dry-run mode - dryRun := c.GlobalBool("dry-run") - // get skip error flag - skipError := c.GlobalBool("skip-error") - // get names or pattern - names, pattern := chaos.GetNamesOrPattern(c) - // get global chaos interval - interval := c.GlobalString("interval") - - // get network interface from parent `netem` command - iface := c.Parent().String("interface") - // get ips list from parent `netem`` command `target` flag - ips := c.Parent().StringSlice("target") - // get egress port list from parent `netem` command `egressPort` flag - sports := c.Parent().String("egressPort") - // get ingress port list from parent `netem` command `ingressPort` flag - dports := c.Parent().String("ingressPort") - // get duration from parent `netem`` command - duration := c.Parent().String("duration") - // get traffic control image from parent `netem` command - image := c.Parent().String("tc-image") - // get pull tc image flag - pull := c.Parent().BoolT("pull-image") - // get limit for number of containers to netem - limit := c.Parent().Int("limit") - + // parse common chaos flags + globalParams, err := chaos.ParseGlobalParams(c) + if err != nil { + return errors.Wrap(err, "error parsing global parameters") + } + // parse netem flags + netemParams, err := parseNetemParams(c.Parent(), globalParams.Interval) + if err != nil { + return errors.Wrap(err, "error parsing netem parameters") + } // get delay time time := c.Int("time") // get delay jitter @@ -90,10 +71,14 @@ func (cmd *delayContext) delay(c *cli.Context) error { distribution := c.String("distribution") // init netem delay command - delayCommand, err := netem.NewDelayCommand(chaos.DockerClient, names, pattern, labels, iface, ips, sports, dports, duration, interval, time, jitter, correlation, distribution, image, pull, limit, dryRun) + delayCommand, err := netem.NewDelayCommand(chaos.DockerClient, globalParams, netemParams, time, jitter, correlation, distribution) if err != nil { - return err + return errors.Wrap(err, "error creating netem delay command") } // run netem delay command - return chaos.RunChaosCommand(cmd.context, delayCommand, interval, random, skipError) + err = chaos.RunChaosCommand(cmd.context, delayCommand, globalParams) + if err != nil { + return errors.Wrap(err, "error running netem delay command") + } + return nil } diff --git a/pkg/chaos/netem/cmd/duplicate.go b/pkg/chaos/netem/cmd/duplicate.go index 559367f4..23cfd0d8 100644 --- a/pkg/chaos/netem/cmd/duplicate.go +++ b/pkg/chaos/netem/cmd/duplicate.go @@ -4,10 +4,14 @@ import ( "context" "fmt" - "github.com/urfave/cli" - "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/chaos/netem" + "github.com/pkg/errors" + "github.com/urfave/cli" +) + +const ( + duplicateCmd = "duplicate" ) type duplicateContext struct { @@ -18,7 +22,7 @@ type duplicateContext struct { func NewDuplicateCLICommand(ctx context.Context) *cli.Command { cmdContext := &duplicateContext{context: ctx} return &cli.Command{ - Name: "duplicate", + Name: duplicateCmd, Flags: []cli.Flag{ cli.Float64Flag{ Name: "percent, p", @@ -40,46 +44,30 @@ func NewDuplicateCLICommand(ctx context.Context) *cli.Command { // NETEM Duplicate Command - network emulation duplicate func (cmd *duplicateContext) duplicate(c *cli.Context) error { - // get random flag - random := c.GlobalBool("random") - // get labels - labels := c.GlobalStringSlice("label") - // get dry-run mode - dryRun := c.GlobalBool("dry-run") - // get skip error flag - skipError := c.GlobalBool("skip-error") - // get names or pattern - names, pattern := chaos.GetNamesOrPattern(c) - // get global chaos interval - interval := c.GlobalString("interval") - - // get network interface from parent `netem` command - iface := c.Parent().String("interface") - // get ips list from parent `netem`` command `target` flag - ips := c.Parent().StringSlice("target") - // get egress port list from parent `netem` command `egressPort` flag - sports := c.Parent().String("egressPort") - // get ingress port list from parent `netem` command `ingressPort` flag - dports := c.Parent().String("ingressPort") - // get duration from parent `netem`` command - duration := c.Parent().String("duration") - // get traffic control image from parent `netem` command - image := c.Parent().String("tc-image") - // get pull tc image flag - pull := c.Parent().BoolT("pull-image") - // get limit for number of containers to netem - limit := c.Parent().Int("limit") - + // parse common chaos flags + globalParams, err := chaos.ParseGlobalParams(c) + if err != nil { + return errors.Wrap(err, "error parsing global parameters") + } + // parse netem flags + netemParams, err := parseNetemParams(c.Parent(), globalParams.Interval) + if err != nil { + return errors.Wrap(err, "error parsing netem parameters") + } // get duplicate percentage percent := c.Float64("percent") // get delay variation correlation := c.Float64("correlation") // init netem duplicate command - duplicateCommand, err := netem.NewDuplicateCommand(chaos.DockerClient, names, pattern, labels, iface, ips, sports, dports, duration, interval, percent, correlation, image, pull, limit, dryRun) + duplicateCommand, err := netem.NewDuplicateCommand(chaos.DockerClient, globalParams, netemParams, percent, correlation) if err != nil { - return err + return errors.Wrap(err, "unable to create netem duplicate command") } // run netem command - return chaos.RunChaosCommand(cmd.context, duplicateCommand, interval, random, skipError) + err = chaos.RunChaosCommand(cmd.context, duplicateCommand, globalParams) + if err != nil { + return errors.Wrap(err, "error running netem duplicate command") + } + return nil } diff --git a/pkg/chaos/netem/cmd/loss.go b/pkg/chaos/netem/cmd/loss.go index 92a7fbe0..faa44f1e 100644 --- a/pkg/chaos/netem/cmd/loss.go +++ b/pkg/chaos/netem/cmd/loss.go @@ -4,10 +4,10 @@ import ( "context" "fmt" - "github.com/urfave/cli" - "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/chaos/netem" + "github.com/pkg/errors" + "github.com/urfave/cli" ) type lossContext struct { @@ -40,46 +40,29 @@ func NewLossCLICommand(ctx context.Context) *cli.Command { // NETEM LOSS Command - network emulation loss func (cmd *lossContext) loss(c *cli.Context) error { - // get random flag - random := c.GlobalBool("random") - // get labels - labels := c.GlobalStringSlice("label") - // get dry-run mode - dryRun := c.GlobalBool("dry-run") - // get skip error flag - skipError := c.GlobalBool("skip-error") - // get names or pattern - names, pattern := chaos.GetNamesOrPattern(c) - // get global chaos interval - interval := c.GlobalString("interval") - - // get network interface from parent `netem` command - iface := c.Parent().String("interface") - // get ips list from parent `netem`` command `target` flag - ips := c.Parent().StringSlice("target") - // get egress port list from parent `netem` command `egressPort` flag - sports := c.Parent().String("egressPort") - // get ingress port list from parent `netem` command `ingressPort` flag - dports := c.Parent().String("ingressPort") - // get duration from parent `netem`` command - duration := c.Parent().String("duration") - // get traffic control image from parent `netem` command - image := c.Parent().String("tc-image") - // get pull tc image flag - pull := c.Parent().BoolT("pull-image") - // get limit for number of containers to netem - limit := c.Parent().Int("limit") - + // parse common chaos flags + globalParams, err := chaos.ParseGlobalParams(c) + if err != nil { + return errors.Wrap(err, "error parsing global parameters") + } + // parse netem flags + netemParams, err := parseNetemParams(c.Parent(), globalParams.Interval) + if err != nil { + return errors.Wrap(err, "error parsing netem parameters") + } // get loss percentage percent := c.Float64("percent") // get delay variation correlation := c.Float64("correlation") - // init netem loss command - lossCommand, err := netem.NewLossCommand(chaos.DockerClient, names, pattern, labels, iface, ips, sports, dports, duration, interval, percent, correlation, image, pull, limit, dryRun) + lossCommand, err := netem.NewLossCommand(chaos.DockerClient, globalParams, netemParams, percent, correlation) if err != nil { - return err + return errors.Wrap(err, "error creating netem loss command") } // run netem command - return chaos.RunChaosCommand(cmd.context, lossCommand, interval, random, skipError) + err = chaos.RunChaosCommand(cmd.context, lossCommand, globalParams) + if err != nil { + return errors.Wrap(err, "error running netem loss command") + } + return nil } diff --git a/pkg/chaos/netem/cmd/loss_ge.go b/pkg/chaos/netem/cmd/loss_ge.go index 7f29ecf3..69ec3acc 100644 --- a/pkg/chaos/netem/cmd/loss_ge.go +++ b/pkg/chaos/netem/cmd/loss_ge.go @@ -1,13 +1,14 @@ +//nolint:dupl package cmd import ( "context" "fmt" - "github.com/urfave/cli" - "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/chaos/netem" + "github.com/pkg/errors" + "github.com/urfave/cli" ) type lossGEContext struct { @@ -28,12 +29,12 @@ func NewLossGECLICommand(ctx context.Context) *cli.Command { cli.Float64Flag{ Name: "pb, r", Usage: "transition probability into the good state", - Value: 100.0, + Value: 100.0, //nolint:gomnd }, cli.Float64Flag{ Name: "one-h", Usage: "loss probability in the bad state", - Value: 100.0, + Value: 100.0, //nolint:gomnd }, cli.Float64Flag{ Name: "one-k", @@ -51,36 +52,16 @@ func NewLossGECLICommand(ctx context.Context) *cli.Command { // NETEM LOSS GEMODEL Command - network emulation loss by Gilbert-Elliot model func (cmd *lossGEContext) lossGE(c *cli.Context) error { - // get random flag - random := c.GlobalBool("random") - // get labels - labels := c.GlobalStringSlice("label") - // get dry-run mode - dryRun := c.GlobalBool("dry-run") - // get skip error flag - skipError := c.GlobalBool("skip-error") - // get names or pattern - names, pattern := chaos.GetNamesOrPattern(c) - // get global chaos interval - interval := c.GlobalString("interval") - - // get network interface from parent `netem` command - iface := c.Parent().String("interface") - // get ips list from parent `netem`` command `target` flag - ips := c.Parent().StringSlice("target") - // get egress port list from parent `netem` command `egressPort` flag - sports := c.Parent().String("egressPort") - // get ingress port list from parent `netem` command `ingressPort` flag - dports := c.Parent().String("ingressPort") - // get duration from parent `netem`` command - duration := c.Parent().String("duration") - // get traffic control image from parent `netem` command - image := c.Parent().String("tc-image") - // get pull tc image flag - pull := c.Parent().BoolT("pull-image") - // get limit for number of containers to netem - limit := c.Parent().Int("limit") - + // parse common chaos flags + globalParams, err := chaos.ParseGlobalParams(c) + if err != nil { + return errors.Wrap(err, "error parsing global parameters") + } + // parse netem flags + netemParams, err := parseNetemParams(c.Parent(), globalParams.Interval) + if err != nil { + return errors.Wrap(err, "error parsing netem parameters") + } // Good State transition probability pg := c.Float64("pg") // Bad State transition probability @@ -91,10 +72,14 @@ func (cmd *lossGEContext) lossGE(c *cli.Context) error { oneK := c.Float64("one-k") // init netem loss gemodel command - lossGECommand, err := netem.NewLossGECommand(chaos.DockerClient, names, pattern, labels, iface, ips, sports, dports, duration, interval, pg, pb, oneH, oneK, image, pull, limit, dryRun) + lossGECommand, err := netem.NewLossGECommand(chaos.DockerClient, globalParams, netemParams, pg, pb, oneH, oneK) if err != nil { - return err + return errors.Wrap(err, "error creating loss gemodel command") } // run netem command - return chaos.RunChaosCommand(cmd.context, lossGECommand, interval, random, skipError) + err = chaos.RunChaosCommand(cmd.context, lossGECommand, globalParams) + if err != nil { + return errors.Wrap(err, "error running netem loss gemodel command") + } + return nil } diff --git a/pkg/chaos/netem/cmd/loss_state.go b/pkg/chaos/netem/cmd/loss_state.go index a1a49b29..306e15cb 100644 --- a/pkg/chaos/netem/cmd/loss_state.go +++ b/pkg/chaos/netem/cmd/loss_state.go @@ -4,10 +4,10 @@ import ( "context" "fmt" - "github.com/urfave/cli" - "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/chaos/netem" + "github.com/pkg/errors" + "github.com/urfave/cli" ) type lossStateContext struct { @@ -28,7 +28,7 @@ func NewLossStateCLICommand(ctx context.Context) *cli.Command { cli.Float64Flag{ Name: "p31", Usage: "probability to go from state (3) to state (1)", - Value: 100.0, + Value: 100.0, //nolint:gomnd }, cli.Float64Flag{ Name: "p32", @@ -38,7 +38,7 @@ func NewLossStateCLICommand(ctx context.Context) *cli.Command { cli.Float64Flag{ Name: "p23", Usage: "probability to go from state (2) to state (3)", - Value: 100.0, + Value: 100.0, //nolint:gomnd }, cli.Float64Flag{ Name: "p14", @@ -55,43 +55,23 @@ func NewLossStateCLICommand(ctx context.Context) *cli.Command { state (3) – packet lost within a burst tstate (4) – isolated packet lost within a gap - see detailed description: http://www.voiptroubleshooter.com/indepth/burstloss.html`, + see detailed description: https://www.voiptroubleshooter.com/indepth/burstloss.html`, Action: cmdContext.lossState, } } // NETEM LOSS STATE Command - network emulation loss 4-state Markov func (cmd *lossStateContext) lossState(c *cli.Context) error { - // get random flag - random := c.GlobalBool("random") - // get labels - labels := c.GlobalStringSlice("label") - // get dry-run mode - dryRun := c.GlobalBool("dry-run") - // get skip error flag - skipError := c.GlobalBool("skip-error") - // get names or pattern - names, pattern := chaos.GetNamesOrPattern(c) - // get global chaos interval - interval := c.GlobalString("interval") - - // get network interface from parent `netem` command - iface := c.Parent().String("interface") - // get ips list from parent `netem`` command `target` flag - ips := c.Parent().StringSlice("target") - // get egress port list from parent `netem` command `egressPort` flag - sports := c.Parent().String("egressPort") - // get ingress port list from parent `netem` command `ingressPort` flag - dports := c.Parent().String("ingressPort") - // get duration from parent `netem`` command - duration := c.Parent().String("duration") - // get traffic control image from parent `netem` command - image := c.Parent().String("tc-image") - // get pull tc image flag - pull := c.Parent().BoolT("pull-image") - // get limit for number of containers to netem - limit := c.Parent().Int("limit") - + // parse common chaos flags + globalParams, err := chaos.ParseGlobalParams(c) + if err != nil { + return errors.Wrap(err, "error parsing global parameters") + } + // parse netem flags + netemParams, err := parseNetemParams(c.Parent(), globalParams.Interval) + if err != nil { + return errors.Wrap(err, "error parsing netem parameters") + } // get loss p13 state probability p13 := c.Float64("p13") // get loss p31 state probability @@ -104,10 +84,14 @@ func (cmd *lossStateContext) lossState(c *cli.Context) error { p14 := c.Float64("p14") // init netem loss state command - lossStateCommand, err := netem.NewLossStateCommand(chaos.DockerClient, names, pattern, labels, iface, ips, sports, dports, duration, interval, p13, p31, p32, p23, p14, image, pull, limit, dryRun) + lossStateCommand, err := netem.NewLossStateCommand(chaos.DockerClient, globalParams, netemParams, p13, p31, p32, p23, p14) if err != nil { - return err + return errors.Wrap(err, "error creating netem loss state command") } // run netem command - return chaos.RunChaosCommand(cmd.context, lossStateCommand, interval, random, skipError) + err = chaos.RunChaosCommand(cmd.context, lossStateCommand, globalParams) + if err != nil { + return errors.Wrap(err, "error running netem loss state command") + } + return nil } diff --git a/pkg/chaos/netem/cmd/netem.go b/pkg/chaos/netem/cmd/netem.go new file mode 100644 index 00000000..0d1fb488 --- /dev/null +++ b/pkg/chaos/netem/cmd/netem.go @@ -0,0 +1,60 @@ +package cmd + +import ( + "net" + "regexp" + "time" + + "github.com/alexei-led/pumba/pkg/chaos/netem" + "github.com/alexei-led/pumba/pkg/util" + "github.com/pkg/errors" + "github.com/urfave/cli" +) + +func parseNetemParams(c *cli.Context, interval time.Duration) (*netem.Params, error) { + // get duration + duration := c.Duration("duration") + if duration == 0 { + return nil, errors.New("unset or invalid duration value") + } + if interval != 0 && duration >= interval { + return nil, errors.New("duration must be shorter than interval") + } + // protect from Command Injection, using Regexp + iface := c.String("interface") + reInterface := regexp.MustCompile(`[a-zA-Z][a-zA-Z0-9.:_-]*`) + validIface := reInterface.FindString(iface) + if iface != validIface { + return nil, errors.Errorf("bad network interface name: must match '%s'", reInterface.String()) + } + // validate ips + ipsList := c.StringSlice("target") + ips := make([]*net.IPNet, 0, len(ipsList)) + for _, str := range ipsList { + ip, e := util.ParseCIDR(str) + if e != nil { + return nil, errors.Wrap(e, "failed to parse ip") + } + ips = append(ips, ip) + } + // validate source ports + sports, err := util.GetPorts(c.String("egress-port")) + if err != nil { + return nil, errors.Wrap(err, "failed to get source ports") + } + // validate destination ports + dports, err := util.GetPorts(c.String("ingress-port")) + if err != nil { + return nil, errors.Wrap(err, "failed to get destination ports") + } + return &netem.Params{ + Iface: iface, + Ips: ips, + Sports: sports, + Dports: dports, + Duration: duration, + Image: c.String("tc-image"), + Pull: c.Bool("pull-image"), + Limit: c.Int("limit"), + }, nil +} diff --git a/pkg/chaos/netem/cmd/rate.go b/pkg/chaos/netem/cmd/rate.go index 0242e93b..1c737724 100644 --- a/pkg/chaos/netem/cmd/rate.go +++ b/pkg/chaos/netem/cmd/rate.go @@ -1,13 +1,14 @@ +//nolint:dupl package cmd import ( "context" "fmt" - "github.com/urfave/cli" - "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/chaos/netem" + "github.com/pkg/errors" + "github.com/urfave/cli" ) type rateContext struct { @@ -50,36 +51,16 @@ func NewRateCLICommand(ctx context.Context) *cli.Command { // NETEM RATE Command - network emulation rate func (cmd *rateContext) rate(c *cli.Context) error { - // get random flag - random := c.GlobalBool("random") - // get labels - labels := c.GlobalStringSlice("label") - // get dry-run mode - dryRun := c.GlobalBool("dry-run") - // get skip error flag - skipError := c.GlobalBool("skip-error") - // get names or pattern - names, pattern := chaos.GetNamesOrPattern(c) - // get global chaos interval - interval := c.GlobalString("interval") - - // get network interface from parent `netem` command - iface := c.Parent().String("interface") - // get ips list from parent `netem`` command `target` flag - ips := c.Parent().StringSlice("target") - // get egress port list from parent `netem` command `egressPort` flag - sports := c.Parent().String("egressPort") - // get ingress port list from parent `netem` command `ingressPort` flag - dports := c.Parent().String("ingressPort") - // get duration from parent `netem`` command - duration := c.Parent().String("duration") - // get traffic control image from parent `netem` command - image := c.Parent().String("tc-image") - // get pull tc image flag - pull := c.Parent().BoolT("pull-image") - // get limit for number of containers to netem command - limit := c.Parent().Int("limit") - + // parse common chaos flags + globalParams, err := chaos.ParseGlobalParams(c) + if err != nil { + return errors.Wrap(err, "error parsing global parameters") + } + // parse netem flags + netemParams, err := parseNetemParams(c.Parent(), globalParams.Interval) + if err != nil { + return errors.Wrap(err, "error parsing netem parameters") + } // get target egress rate rate := c.String("rate") // get packet overhead @@ -90,10 +71,14 @@ func (cmd *rateContext) rate(c *cli.Context) error { cellOverhead := c.Int("celloverhead") // init netem rate command - lossCommand, err := netem.NewRateCommand(chaos.DockerClient, names, pattern, labels, iface, ips, sports, dports, duration, interval, rate, packetOverhead, cellSize, cellOverhead, image, pull, limit, dryRun) + lossCommand, err := netem.NewRateCommand(chaos.DockerClient, globalParams, netemParams, rate, packetOverhead, cellSize, cellOverhead) if err != nil { - return err + return errors.Wrap(err, "error creating netem rate command") } // run netem command - return chaos.RunChaosCommand(cmd.context, lossCommand, interval, random, skipError) + err = chaos.RunChaosCommand(cmd.context, lossCommand, globalParams) + if err != nil { + return errors.Wrap(err, "error running netem rate command") + } + return nil } diff --git a/pkg/chaos/netem/corrupt.go b/pkg/chaos/netem/corrupt.go index 9e6134b1..c8fe78e0 100644 --- a/pkg/chaos/netem/corrupt.go +++ b/pkg/chaos/netem/corrupt.go @@ -3,21 +3,18 @@ package netem import ( "context" "net" - "regexp" "strconv" "sync" "time" "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/container" - "github.com/alexei-led/pumba/pkg/util" "github.com/pkg/errors" - log "github.com/sirupsen/logrus" ) -// CorruptCommand `netem corrupt` command -type CorruptCommand struct { +// `netem corrupt` command +type corruptCommand struct { client container.Client names []string pattern string @@ -37,57 +34,11 @@ type CorruptCommand struct { // NewCorruptCommand create new netem corrupt command func NewCorruptCommand(client container.Client, - names []string, // containers - pattern string, // re2 regex pattern - labels []string, // filter by labels - iface string, // network interface - ipsList []string, // list of target ips - sportsList, // list of comma separated target sports - dportsList, // list of comma separated target dports - durationStr, // chaos duration - intervalStr string, // repeatable chaos interval + globalParams *chaos.GlobalParams, + netemParams *Params, percent, // corrupt percent correlation float64, // corrupt correlation - image string, // traffic control image - pull bool, // pull tc image - limit int, // limit chaos to containers - dryRun bool, // dry-run do not netem just log ) (chaos.Command, error) { - // get interval - interval, err := util.GetIntervalValue(intervalStr) - if err != nil { - return nil, err - } - // get duration - duration, err := util.GetDurationValue(durationStr, interval) - if err != nil { - return nil, err - } - // protect from Command Injection, using Regexp - reInterface := regexp.MustCompile("[a-zA-Z][a-zA-Z0-9\\.:_-]*") - validIface := reInterface.FindString(iface) - if iface != validIface { - return nil, errors.Errorf("bad network interface name: must match '%s'", reInterface.String()) - } - // validate ips - var ips []*net.IPNet - for _, str := range ipsList { - ip, e := util.ParseCIDR(str) - if e != nil { - return nil, e - } - ips = append(ips, ip) - } - // validate sports - sports, err := util.GetPorts(sportsList) - if err != nil { - return nil, err - } - // validate dports - dports, err := util.GetPorts(dportsList) - if err != nil { - return nil, err - } // get netem corrupt percent if percent < 0.0 || percent > 100.0 { return nil, errors.New("invalid corrupt percent: must be between 0.0 and 100.0") @@ -96,28 +47,27 @@ func NewCorruptCommand(client container.Client, if correlation < 0.0 || correlation > 100.0 { return nil, errors.New("invalid corrupt correlation: must be between 0.0 and 100.0") } - - return &CorruptCommand{ + return &corruptCommand{ client: client, - names: names, - labels: labels, - pattern: pattern, - iface: iface, - ips: ips, - sports: sports, - dports: dports, - duration: duration, + names: globalParams.Names, + labels: globalParams.Labels, + pattern: globalParams.Pattern, + iface: netemParams.Iface, + ips: netemParams.Ips, + sports: netemParams.Sports, + dports: netemParams.Dports, + duration: netemParams.Duration, percent: percent, correlation: correlation, - image: image, - pull: pull, - limit: limit, - dryRun: dryRun, + image: netemParams.Image, + pull: netemParams.Pull, + limit: netemParams.Limit, + dryRun: globalParams.DryRun, }, nil } // Run netem corrupt command -func (n *CorruptCommand) Run(ctx context.Context, random bool) error { +func (n *corruptCommand) Run(ctx context.Context, random bool) error { log.Debug("adding network random packet corrupt to all matching containers") log.WithFields(log.Fields{ "names": n.names, @@ -128,7 +78,7 @@ func (n *CorruptCommand) Run(ctx context.Context, random bool) error { }).Debug("listing matching containers") containers, err := container.ListNContainers(ctx, n.client, n.names, n.pattern, n.labels, n.limit) if err != nil { - return err + return errors.Wrap(err, "error listing containers") } if len(containers) == 0 { log.Warning("no containers found") @@ -143,9 +93,9 @@ func (n *CorruptCommand) Run(ctx context.Context, random bool) error { } // prepare netem corrupt command - netemCmd := []string{"corrupt", strconv.FormatFloat(n.percent, 'f', 2, 64)} + netemCmd := []string{"corrupt", strconv.FormatFloat(n.percent, 'f', 2, 64)} //nolint:gomnd if n.correlation > 0 { - netemCmd = append(netemCmd, strconv.FormatFloat(n.correlation, 'f', 2, 64)) + netemCmd = append(netemCmd, strconv.FormatFloat(n.correlation, 'f', 2, 64)) //nolint:gomnd } // run netem corrupt command for selected containers diff --git a/pkg/chaos/netem/delay.go b/pkg/chaos/netem/delay.go index 0402e728..a7707170 100644 --- a/pkg/chaos/netem/delay.go +++ b/pkg/chaos/netem/delay.go @@ -2,17 +2,13 @@ package netem import ( "context" - "net" - "regexp" "strconv" "sync" - "time" "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/container" "github.com/alexei-led/pumba/pkg/util" "github.com/pkg/errors" - log "github.com/sirupsen/logrus" ) @@ -21,82 +17,24 @@ var ( delayDistribution = []string{"", "uniform", "normal", "pareto", "paretonormal"} ) -// DelayCommand `netem delay` command -type DelayCommand struct { - client container.Client - names []string - pattern string - labels []string - iface string - ips []*net.IPNet - sports []string - dports []string - duration time.Duration +// `netem delay` command +type delayCommand struct { + netemCommand time int jitter int correlation float64 distribution string - image string - pull bool - limit int - dryRun bool } // NewDelayCommand create new netem delay command func NewDelayCommand(client container.Client, - names []string, // containers - pattern string, // re2 regex pattern - labels []string, // filter by labels - iface string, // network interface - ipsList []string, // list of target ips - sportsList, // list of comma separated target sports - dportsList, // list of comma separated target dports - durationStr, // chaos duration - intervalStr string, // repeatable chaos interval + globalParams *chaos.GlobalParams, + netemParams *Params, delay, // delay time jitter int, // delay jitter correlation float64, // delay correlation - distribution, // delay distribution - image string, // traffic control image - pull bool, // pull tc image option - limit int, // limit chaos to containers - dryRun bool, // dry-run do not delay just log + distribution string, // delay distribution ) (chaos.Command, error) { - // get interval - interval, err := util.GetIntervalValue(intervalStr) - if err != nil { - return nil, err - } - // get duration - duration, err := util.GetDurationValue(durationStr, interval) - if err != nil { - return nil, err - } - // protect from Command Injection, using Regexp - reInterface := regexp.MustCompile("[a-zA-Z][a-zA-Z0-9\\.:_-]*") - validIface := reInterface.FindString(iface) - if iface != validIface { - return nil, errors.Errorf("bad network interface name: must match '%s'", reInterface.String()) - } - // validate ips - var ips []*net.IPNet - for _, str := range ipsList { - ip, e := util.ParseCIDR(str) - if e != nil { - return nil, e - } - ips = append(ips, ip) - } - // validate sports - sports, err := util.GetPorts(sportsList) - if err != nil { - return nil, err - } - // validate dports - dports, err := util.GetPorts(dportsList) - if err != nil { - return nil, err - } // check delay time if delay <= 0 { return nil, errors.New("non-positive delay time") @@ -113,30 +51,17 @@ func NewDelayCommand(client container.Client, if ok := util.SliceContains(delayDistribution, distribution); !ok { return nil, errors.New("invalid delay distribution: must be one of {uniform | normal | pareto | paretonormal}") } - - return &DelayCommand{ - client: client, - names: names, - pattern: pattern, - labels: labels, - iface: iface, - ips: ips, - sports: sports, - dports: dports, - duration: duration, + return &delayCommand{ + netemCommand: newNetemCommand(client, globalParams, netemParams), time: delay, jitter: jitter, correlation: correlation, distribution: distribution, - image: image, - pull: pull, - limit: limit, - dryRun: dryRun, }, nil } // Run netem delay command -func (n *DelayCommand) Run(ctx context.Context, random bool) error { +func (n *delayCommand) Run(ctx context.Context, random bool) error { log.Debug("adding network delay to all matching containers") log.WithFields(log.Fields{ "names": n.names, @@ -147,7 +72,7 @@ func (n *DelayCommand) Run(ctx context.Context, random bool) error { }).Debug("listing matching containers") containers, err := container.ListNContainers(ctx, n.client, n.names, n.pattern, n.labels, n.limit) if err != nil { - return err + return errors.Wrap(err, "error listing containers") } if len(containers) == 0 { log.Warning("no containers found") @@ -167,7 +92,7 @@ func (n *DelayCommand) Run(ctx context.Context, random bool) error { netemCmd = append(netemCmd, strconv.Itoa(n.jitter)+"ms") } if n.correlation > 0 { - netemCmd = append(netemCmd, strconv.FormatFloat(n.correlation, 'f', 2, 64)) + netemCmd = append(netemCmd, strconv.FormatFloat(n.correlation, 'f', 2, 64)) //nolint:gomnd } if n.distribution != "" { netemCmd = append(netemCmd, []string{"distribution", n.distribution}...) diff --git a/pkg/chaos/netem/delay_test.go b/pkg/chaos/netem/delay_test.go index 1c6a7388..e0bf823c 100644 --- a/pkg/chaos/netem/delay_test.go +++ b/pkg/chaos/netem/delay_test.go @@ -4,231 +4,13 @@ import ( "context" "errors" "net" - "reflect" "testing" "time" - "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/container" "github.com/stretchr/testify/mock" ) -//nolint:funlen -func TestNewDelayCommand(t *testing.T) { - type args struct { - names []string - pattern string - labels []string - iface string - ipsList []string - sportsList string - dportsList string - durationStr string - intervalStr string - time int - jitter int - correlation float64 - distribution string - image string - pull bool - limit int - dryRun bool - } - tests := []struct { - name string - args args - want chaos.Command - wantErr bool - }{ - { - name: "create Delay command", - args: args{ - names: []string{"n1", "n2"}, - pattern: "re2:test", - iface: "testIface", - ipsList: []string{"1.2.3.4", "5.6.7.8"}, - sportsList: "0,65535", - dportsList: "33,512", - intervalStr: "1m", - durationStr: "30s", - time: 10, - jitter: 2, - correlation: 0.5, - distribution: delayDistribution[2], - image: "test/image", - limit: 2, - }, - want: &DelayCommand{ - client: nil, - names: []string{"n1", "n2"}, - pattern: "re2:test", - iface: "testIface", - ips: []*net.IPNet{ - {IP: net.IP{1, 2, 3, 4}, Mask: net.IPMask{255, 255, 255, 255}}, - {IP: net.IP{5, 6, 7, 8}, Mask: net.IPMask{255, 255, 255, 255}}, - }, - sports: []string{"0", "65535"}, - dports: []string{"33", "512"}, - duration: 30 * time.Second, - time: 10, - jitter: 2, - correlation: 0.5, - distribution: delayDistribution[2], - image: "test/image", - limit: 2, - }, - }, - { - name: "bad interval value", - args: args{ - intervalStr: "bad-interval", - }, - wantErr: true, - }, - { - name: "bad duration value", - args: args{ - intervalStr: "1m", - durationStr: "bad-duration", - }, - wantErr: true, - }, - { - name: "duration is bigger than interval value", - args: args{ - intervalStr: "1m", - durationStr: "2m", - }, - wantErr: true, - }, - { - name: "bad network interface name", - args: args{ - intervalStr: "1m", - durationStr: "30s", - iface: "bad#interface", - }, - wantErr: true, - }, - { - name: "invalid CIDR IP address", - args: args{ - intervalStr: "1m", - durationStr: "30s", - iface: "eth0", - ipsList: []string{"1.2.3.4/3.4.5.6..."}, - }, - wantErr: true, - }, - { - name: "invalid IP address", - args: args{ - intervalStr: "1m", - durationStr: "30s", - iface: "eth0", - ipsList: []string{"1.2.3.4.5.6..."}, - }, - wantErr: true, - }, - { - name: "invalid port number: too high", - args: args{ - intervalStr: "1m", - durationStr: "30s", - iface: "eth0", - sportsList: "70000", - }, - wantErr: true, - }, - { - name: "invalid port number: non-numerical", - args: args{ - intervalStr: "1m", - durationStr: "30s", - iface: "eth0", - dportsList: "!abcd$", - }, - wantErr: true, - }, - { - name: "negative delay time", - args: args{ - intervalStr: "1m", - durationStr: "30s", - iface: "eth0", - ipsList: []string{"1.2.3.4"}, - time: -1, - }, - wantErr: true, - }, - { - name: "negative jitter time", - args: args{ - intervalStr: "1m", - durationStr: "30s", - iface: "eth0", - ipsList: []string{"1.2.3.4"}, - time: 1, - jitter: -1, - }, - wantErr: true, - }, - { - name: "too big jitter time", - args: args{ - intervalStr: "1m", - durationStr: "30s", - iface: "eth0", - ipsList: []string{"1.2.3.4"}, - time: 1, - jitter: 2, - }, - wantErr: true, - }, - { - name: "bad correlation", - args: args{ - intervalStr: "1m", - durationStr: "30s", - iface: "eth0", - ipsList: []string{"1.2.3.4"}, - time: 10, - jitter: 2, - correlation: 101.0, - }, - wantErr: true, - }, - { - name: "bad distribution", - args: args{ - intervalStr: "1m", - durationStr: "30s", - iface: "eth0", - ipsList: []string{"1.2.3.4"}, - time: 10, - jitter: 2, - correlation: 11.0, - distribution: "bad-distribution", - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // invoke - got, err := NewDelayCommand(nil, tt.args.names, tt.args.pattern, tt.args.labels, tt.args.iface, tt.args.ipsList, tt.args.sportsList, tt.args.dportsList, tt.args.durationStr, tt.args.intervalStr, tt.args.time, tt.args.jitter, tt.args.correlation, tt.args.distribution, tt.args.image, tt.args.pull, tt.args.limit, tt.args.dryRun) - if (err != nil) != tt.wantErr { - t.Errorf("NewDelayCommand() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewDelayCommand() = %v, want %v", got, tt.want) - } - }) - } -} - -//nolint:funlen func TestDelayCommand_Run(t *testing.T) { type wantErrors struct { listError bool @@ -389,22 +171,24 @@ func TestDelayCommand_Run(t *testing.T) { for _, tt := range tests { mockClient := new(container.MockClient) t.Run(tt.name, func(t *testing.T) { - n := &DelayCommand{ - client: mockClient, - names: tt.fields.names, - pattern: tt.fields.pattern, - iface: tt.fields.iface, - ips: tt.fields.ips, - sports: tt.fields.sports, - dports: tt.fields.dports, - duration: tt.fields.duration, + n := &delayCommand{ + netemCommand: netemCommand{ + client: mockClient, + names: tt.fields.names, + pattern: tt.fields.pattern, + iface: tt.fields.iface, + ips: tt.fields.ips, + sports: tt.fields.sports, + dports: tt.fields.dports, + duration: tt.fields.duration, + image: tt.fields.image, + limit: tt.fields.limit, + dryRun: tt.fields.dryRun, + }, time: tt.fields.time, jitter: tt.fields.jitter, correlation: tt.fields.correlation, distribution: tt.fields.distribution, - image: tt.fields.image, - limit: tt.fields.limit, - dryRun: tt.fields.dryRun, } // mock calls call := mockClient.On("ListContainers", mock.AnythingOfType("*context.emptyCtx"), mock.AnythingOfType("container.FilterFunc"), mock.AnythingOfType("container.ListOpts")) diff --git a/pkg/chaos/netem/duplicate.go b/pkg/chaos/netem/duplicate.go index 72d237f1..84308722 100644 --- a/pkg/chaos/netem/duplicate.go +++ b/pkg/chaos/netem/duplicate.go @@ -3,21 +3,22 @@ package netem import ( "context" "net" - "regexp" "strconv" "sync" "time" "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/container" - "github.com/alexei-led/pumba/pkg/util" "github.com/pkg/errors" - log "github.com/sirupsen/logrus" ) -// DuplicateCommand `netem duplicate` command -type DuplicateCommand struct { +const ( + duplicateCmd = "duplicate" +) + +// `netem duplicate` command +type duplicateCommand struct { client container.Client names []string pattern string @@ -37,58 +38,11 @@ type DuplicateCommand struct { // NewDuplicateCommand create new netem duplicate command func NewDuplicateCommand(client container.Client, - names []string, // containers - pattern string, // re2 regex pattern - labels []string, // filter by labels - iface string, // network interface - ipsList []string, // list of target ips - sportsList, // list of comma separated target sports - dportsList, // list of comma separated target dports - durationStr, // chaos duration - intervalStr string, // repeatable chaos interval + globalParams *chaos.GlobalParams, + netemParams *Params, percent, // duplicate percent correlation float64, // duplicate correlation - image string, // traffic control image - pull bool, // pull tc image - limit int, // limit chaos to containers - dryRun bool, // dry-run do not netem just log ) (chaos.Command, error) { - // get interval - interval, err := util.GetIntervalValue(intervalStr) - if err != nil { - return nil, err - } - // get duration - duration, err := util.GetDurationValue(durationStr, interval) - if err != nil { - return nil, err - } - // protect from Command Injection, using Regexp - reInterface := regexp.MustCompile("[a-zA-Z][a-zA-Z0-9\\.:_-]*") - validIface := reInterface.FindString(iface) - if iface != validIface { - err = errors.Errorf("bad network interface name: must match '%s'", reInterface.String()) - return nil, err - } - // validate ips - var ips []*net.IPNet - for _, str := range ipsList { - ip, e := util.ParseCIDR(str) - if e != nil { - return nil, e - } - ips = append(ips, ip) - } - // validate sports - sports, err := util.GetPorts(sportsList) - if err != nil { - return nil, err - } - // validate dports - dports, err := util.GetPorts(dportsList) - if err != nil { - return nil, err - } // get netem duplicate percent if percent < 0.0 || percent > 100.0 { return nil, errors.New("invalid duplicate percent: must be between 0.0 and 100.0") @@ -97,28 +51,27 @@ func NewDuplicateCommand(client container.Client, if correlation < 0.0 || correlation > 100.0 { return nil, errors.New("invalid duplicate correlation: must be between 0.0 and 100.0") } - - return &DuplicateCommand{ + return &duplicateCommand{ client: client, - names: names, - pattern: pattern, - labels: labels, - iface: iface, - ips: ips, - sports: sports, - dports: dports, - duration: duration, + names: globalParams.Names, + pattern: globalParams.Pattern, + labels: globalParams.Labels, + iface: netemParams.Iface, + ips: netemParams.Ips, + sports: netemParams.Sports, + dports: netemParams.Dports, + duration: netemParams.Duration, percent: percent, correlation: correlation, - image: image, - limit: limit, - pull: pull, - dryRun: dryRun, + image: netemParams.Image, + limit: netemParams.Limit, + pull: netemParams.Pull, + dryRun: globalParams.DryRun, }, nil } // Run netem duplicate command -func (n *DuplicateCommand) Run(ctx context.Context, random bool) error { +func (n *duplicateCommand) Run(ctx context.Context, random bool) error { log.Debug("adding network random packet duplicates to all matching containers") log.WithFields(log.Fields{ "names": n.names, @@ -129,7 +82,7 @@ func (n *DuplicateCommand) Run(ctx context.Context, random bool) error { }).Debug("listing matching containers") containers, err := container.ListNContainers(ctx, n.client, n.names, n.pattern, n.labels, n.limit) if err != nil { - return err + return errors.Wrap(err, "error listing containers") } if len(containers) == 0 { log.Warning("no containers found") @@ -144,9 +97,9 @@ func (n *DuplicateCommand) Run(ctx context.Context, random bool) error { } // prepare netem duplicate command - netemCmd := []string{"duplicate", strconv.FormatFloat(n.percent, 'f', 2, 64)} + netemCmd := []string{duplicateCmd, strconv.FormatFloat(n.percent, 'f', 2, 64)} //nolint:gomnd if n.correlation > 0 { - netemCmd = append(netemCmd, strconv.FormatFloat(n.correlation, 'f', 2, 64)) + netemCmd = append(netemCmd, strconv.FormatFloat(n.correlation, 'f', 2, 64)) //nolint:gomnd } // run netem duplicate command for selected containers @@ -180,7 +133,7 @@ func (n *DuplicateCommand) Run(ctx context.Context, random bool) error { }() // scan through all errors in goroutines - for _, err := range errs { + for _, err = range errs { // take first found error if err != nil { return errors.Wrap(err, "failed to set packet duplicates for one or more containers") diff --git a/pkg/chaos/netem/loss.go b/pkg/chaos/netem/loss.go index ffa7e02c..670a5542 100644 --- a/pkg/chaos/netem/loss.go +++ b/pkg/chaos/netem/loss.go @@ -2,92 +2,29 @@ package netem import ( "context" - "net" - "regexp" "strconv" "sync" - "time" "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/container" - "github.com/alexei-led/pumba/pkg/util" "github.com/pkg/errors" - log "github.com/sirupsen/logrus" ) -// LossCommand `netem loss` command -type LossCommand struct { - client container.Client - names []string - pattern string - labels []string - iface string - ips []*net.IPNet - sports []string - dports []string - duration time.Duration +// `netem loss` command +type lossCommand struct { + netemCommand percent float64 correlation float64 - image string - pull bool - limit int - dryRun bool } // NewLossCommand create new netem loss command func NewLossCommand(client container.Client, - names []string, // containers - pattern string, // re2 regex pattern - labels []string, // filter by labels - iface string, // network interface - ipsList []string, // list of target ips - sportsList, // list of comma separated target sports - dportsList, // list of comma separated target dports - durationStr, // chaos duration - intervalStr string, // repeatable chaos interval + globalParams *chaos.GlobalParams, + netemParams *Params, percent, // loss percent correlation float64, // loss correlation - image string, // traffic control image - pull bool, // pull tc image - limit int, // limit chaos to containers - dryRun bool, // dry-run do not netem just log ) (chaos.Command, error) { - // get interval - interval, err := util.GetIntervalValue(intervalStr) - if err != nil { - return nil, err - } - // get duration - duration, err := util.GetDurationValue(durationStr, interval) - if err != nil { - return nil, err - } - // protect from Command Injection, using Regexp - reInterface := regexp.MustCompile("[a-zA-Z][a-zA-Z0-9\\.:_-]*") - validIface := reInterface.FindString(iface) - if iface != validIface { - return nil, errors.Errorf("bad network interface name: must match '%s'", reInterface.String()) - } - // validate ips - var ips []*net.IPNet - for _, str := range ipsList { - ip, e := util.ParseCIDR(str) - if e != nil { - return nil, e - } - ips = append(ips, ip) - } - // validate sports - sports, err := util.GetPorts(sportsList) - if err != nil { - return nil, err - } - // validate dports - dports, err := util.GetPorts(dportsList) - if err != nil { - return nil, err - } // get netem loss percent if percent < 0.0 || percent > 100.0 { return nil, errors.New("invalid loss percent: must be between 0.0 and 100.0") @@ -97,27 +34,15 @@ func NewLossCommand(client container.Client, return nil, errors.New("invalid loss correlation: must be between 0.0 and 100.0") } - return &LossCommand{ - client: client, - names: names, - pattern: pattern, - labels: labels, - iface: iface, - ips: ips, - sports: sports, - dports: dports, - duration: duration, - percent: percent, - correlation: correlation, - image: image, - pull: pull, - limit: limit, - dryRun: dryRun, + return &lossCommand{ + netemCommand: newNetemCommand(client, globalParams, netemParams), + percent: percent, + correlation: correlation, }, nil } // Run netem loss command -func (n *LossCommand) Run(ctx context.Context, random bool) error { +func (n *lossCommand) Run(ctx context.Context, random bool) error { log.Debug("adding network random packet loss to all matching containers") log.WithFields(log.Fields{ "names": n.names, @@ -128,7 +53,7 @@ func (n *LossCommand) Run(ctx context.Context, random bool) error { }).Debug("listing matching containers") containers, err := container.ListNContainers(ctx, n.client, n.names, n.pattern, n.labels, n.limit) if err != nil { - return err + return errors.Wrap(err, "error listing containers") } if len(containers) == 0 { log.Warning("no containers found") @@ -143,9 +68,9 @@ func (n *LossCommand) Run(ctx context.Context, random bool) error { } // prepare netem loss command - netemCmd := []string{"loss", strconv.FormatFloat(n.percent, 'f', 2, 64)} + netemCmd := []string{"loss", strconv.FormatFloat(n.percent, 'f', 2, 64)} //nolint:gomnd if n.correlation > 0 { - netemCmd = append(netemCmd, strconv.FormatFloat(n.correlation, 'f', 2, 64)) + netemCmd = append(netemCmd, strconv.FormatFloat(n.correlation, 'f', 2, 64)) //nolint:gomnd } // run netem loss command for selected containers @@ -154,7 +79,7 @@ func (n *LossCommand) Run(ctx context.Context, random bool) error { cancels := make([]context.CancelFunc, len(containers)) for i, c := range containers { log.WithFields(log.Fields{ - "container": c, + "container": *c, }).Debug("adding network random packet loss for container") netemCtx, cancel := context.WithTimeout(ctx, n.duration) cancels[i] = cancel @@ -179,7 +104,7 @@ func (n *LossCommand) Run(ctx context.Context, random bool) error { }() // scan through all errors in goroutines - for _, err := range errs { + for _, err = range errs { // take first found error if err != nil { return errors.Wrap(err, "failed to add packet loss for one or more containers") diff --git a/pkg/chaos/netem/loss_ge.go b/pkg/chaos/netem/loss_ge.go index de239f62..8a099685 100644 --- a/pkg/chaos/netem/loss_ge.go +++ b/pkg/chaos/netem/loss_ge.go @@ -2,97 +2,33 @@ package netem import ( "context" - "net" - "regexp" "strconv" "sync" - "time" "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/container" - "github.com/alexei-led/pumba/pkg/util" "github.com/pkg/errors" - log "github.com/sirupsen/logrus" ) -// LossGECommand `netem loss gemodel` (Gilbert-Elliot model) command -type LossGECommand struct { - client container.Client - names []string - pattern string - labels []string - iface string - ips []*net.IPNet - sports []string - dports []string - duration time.Duration - pg float64 - pb float64 - oneH float64 - oneK float64 - image string - pull bool - limit int - dryRun bool +// netem loss gemodel` (Gilbert-Elliot model) command +type lossGECommand struct { + netemCommand + pg float64 + pb float64 + oneH float64 + oneK float64 } // NewLossGECommand create new netem loss gemodel (Gilbert-Elliot) command func NewLossGECommand(client container.Client, - names []string, // containers - pattern string, // re2 regex pattern - labels []string, // filter by labels - iface string, // network interface - ipsList []string, // list of target ips - sportsList, // list of comma separated target sports - dportsList, // list of comma separated target dports - durationStr, // chaos duration - intervalStr string, // repeatable chaos interval + globalParams *chaos.GlobalParams, + netemParams *Params, pg, // Good State transition probability pb, // Bad State transition probability oneH, // loss probability in Bad state oneK float64, // loss probability in Good state - image string, // traffic control image - pull bool, // pull tc image - limit int, // limit chaos to containers - dryRun bool, // dry-run do not netem just log ) (chaos.Command, error) { - // get interval - interval, err := util.GetIntervalValue(intervalStr) - if err != nil { - return nil, err - } - // get duration - duration, err := util.GetDurationValue(durationStr, interval) - if err != nil { - return nil, err - } - // protect from Command Injection, using Regexp - reInterface := regexp.MustCompile("[a-zA-Z][a-zA-Z0-9\\.:_-]*") - validIface := reInterface.FindString(iface) - if iface != validIface { - err = errors.Errorf("bad network interface name: must match '%s'", reInterface.String()) - return nil, err - } - // validate ips - var ips []*net.IPNet - for _, str := range ipsList { - ip, e := util.ParseCIDR(str) - if e != nil { - return nil, e - } - ips = append(ips, ip) - } - // validate sports - sports, err := util.GetPorts(sportsList) - if err != nil { - return nil, err - } - // validate dports - dports, err := util.GetPorts(dportsList) - if err != nil { - return nil, err - } // get pg - Good State transition probability if pg < 0.0 || pg > 100.0 { return nil, errors.New("invalid pg (Good State) transition probability: must be between 0.0 and 100.0") @@ -110,29 +46,17 @@ func NewLossGECommand(client container.Client, return nil, errors.New("invalid loss probability: must be between 0.0 and 100.0") } - return &LossGECommand{ - client: client, - names: names, - pattern: pattern, - labels: labels, - iface: iface, - ips: ips, - sports: sports, - dports: dports, - duration: duration, - pg: pg, - pb: pb, - oneH: oneH, - oneK: oneK, - image: image, - pull: pull, - limit: limit, - dryRun: dryRun, + return &lossGECommand{ + netemCommand: newNetemCommand(client, globalParams, netemParams), + pg: pg, + pb: pb, + oneH: oneH, + oneK: oneK, }, nil } // Run netem loss state command -func (n *LossGECommand) Run(ctx context.Context, random bool) error { +func (n *lossGECommand) Run(ctx context.Context, random bool) error { log.Debug("adding network packet loss according Gilbert-Elliot model to all matching containers") log.WithFields(log.Fields{ "names": n.names, @@ -143,7 +67,7 @@ func (n *LossGECommand) Run(ctx context.Context, random bool) error { }).Debug("listing matching containers") containers, err := container.ListNContainers(ctx, n.client, n.names, n.pattern, n.labels, n.limit) if err != nil { - return err + return errors.Wrap(err, "error listing containers") } if len(containers) == 0 { log.Warning("no containers found") @@ -158,8 +82,11 @@ func (n *LossGECommand) Run(ctx context.Context, random bool) error { } // prepare netem loss gemodel command - netemCmd := []string{"loss", "gemodel", strconv.FormatFloat(n.pg, 'f', 2, 64)} - netemCmd = append(netemCmd, strconv.FormatFloat(n.pb, 'f', 2, 64), strconv.FormatFloat(n.oneH, 'f', 2, 64), strconv.FormatFloat(n.oneK, 'f', 2, 64)) + netemCmd := []string{"loss", "gemodel", strconv.FormatFloat(n.pg, 'f', 2, 64)} //nolint:gomnd + netemCmd = append(netemCmd, + strconv.FormatFloat(n.pb, 'f', 2, 64), //nolint:gomnd + strconv.FormatFloat(n.oneH, 'f', 2, 64), //nolint:gomnd + strconv.FormatFloat(n.oneK, 'f', 2, 64)) //nolint:gomnd // run netem loss command for selected containers var wg sync.WaitGroup diff --git a/pkg/chaos/netem/loss_state.go b/pkg/chaos/netem/loss_state.go index e9723f97..d2fd9ef9 100644 --- a/pkg/chaos/netem/loss_state.go +++ b/pkg/chaos/netem/loss_state.go @@ -2,98 +2,35 @@ package netem import ( "context" - "net" - "regexp" "strconv" "sync" - "time" "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/container" - "github.com/alexei-led/pumba/pkg/util" "github.com/pkg/errors" - log "github.com/sirupsen/logrus" ) -// LossStateCommand `netem loss state` command -type LossStateCommand struct { - client container.Client - names []string - pattern string - labels []string - iface string - ips []*net.IPNet - sports []string - dports []string - duration time.Duration - p13 float64 - p31 float64 - p32 float64 - p23 float64 - p14 float64 - image string - pull bool - limit int - dryRun bool +// `netem loss state` command +type lossStateCommand struct { + netemCommand + p13 float64 + p31 float64 + p32 float64 + p23 float64 + p14 float64 } // NewLossStateCommand create new netem loss state command func NewLossStateCommand(client container.Client, - names []string, // containers - pattern string, // re2 regex pattern - labels []string, // filter by labels - iface string, // network interface - ipsList []string, // list of target ips - sportsList, // list of comma separated target sports - dportsList, // list of comma separated target dports - durationStr, // chaos duration - intervalStr string, // repeatable chaos interval + globalParams *chaos.GlobalParams, + netemParams *Params, p13, // probability to go from state (1) to state (3) p31, // probability to go from state (3) to state (1) p32, // probability to go from state (3) to state (2) p23, // probability to go from state (2) to state (3) p14 float64, // probability to go from state (1) to state (4) - image string, // traffic control image - pull bool, // pull tc image - limit int, // limit chaos to containers - dryRun bool, // dry-run do not netem just log ) (chaos.Command, error) { - // get interval - interval, err := util.GetIntervalValue(intervalStr) - if err != nil { - return nil, err - } - // get duration - duration, err := util.GetDurationValue(durationStr, interval) - if err != nil { - return nil, err - } - // protect from Command Injection, using Regexp - reInterface := regexp.MustCompile("[a-zA-Z][a-zA-Z0-9\\.:_-]*") - validIface := reInterface.FindString(iface) - if iface != validIface { - return nil, errors.Errorf("bad network interface name: must match '%s'", reInterface.String()) - } - // validate ips - var ips []*net.IPNet - for _, str := range ipsList { - ip, e := util.ParseCIDR(str) - if e != nil { - return nil, e - } - ips = append(ips, ip) - } - // validate sports - sports, err := util.GetPorts(sportsList) - if err != nil { - return nil, err - } - // validate dports - dports, err := util.GetPorts(dportsList) - if err != nil { - return nil, err - } // validate p13 if p13 < 0.0 || p13 > 100.0 { return nil, errors.New("invalid p13 percentage: : must be between 0.0 and 100.0") @@ -115,30 +52,18 @@ func NewLossStateCommand(client container.Client, return nil, errors.New("invalid p14 percentage: : must be between 0.0 and 100.0") } - return &LossStateCommand{ - client: client, - names: names, - pattern: pattern, - labels: labels, - iface: iface, - ips: ips, - sports: sports, - dports: dports, - duration: duration, - p13: p13, - p31: p31, - p32: p32, - p23: p23, - p14: p14, - image: image, - pull: pull, - limit: limit, - dryRun: dryRun, + return &lossStateCommand{ + netemCommand: newNetemCommand(client, globalParams, netemParams), + p13: p13, + p31: p31, + p32: p32, + p23: p23, + p14: p14, }, nil } // Run netem loss state command -func (n *LossStateCommand) Run(ctx context.Context, random bool) error { +func (n *lossStateCommand) Run(ctx context.Context, random bool) error { log.Debug("adding network packet loss according 4-state Markov model to all matching containers") log.WithFields(log.Fields{ "names": n.names, @@ -149,7 +74,7 @@ func (n *LossStateCommand) Run(ctx context.Context, random bool) error { }).Debug("listing matching containers") containers, err := container.ListNContainers(ctx, n.client, n.names, n.pattern, n.labels, n.limit) if err != nil { - return err + return errors.Wrap(err, "error listing containers") } if len(containers) == 0 { log.Warning("no containers found") @@ -164,8 +89,12 @@ func (n *LossStateCommand) Run(ctx context.Context, random bool) error { } // prepare netem loss state command - netemCmd := []string{"loss", "state", strconv.FormatFloat(n.p13, 'f', 2, 64)} - netemCmd = append(netemCmd, strconv.FormatFloat(n.p31, 'f', 2, 64), strconv.FormatFloat(n.p32, 'f', 2, 64), strconv.FormatFloat(n.p23, 'f', 2, 64), strconv.FormatFloat(n.p14, 'f', 2, 64)) + netemCmd := []string{"loss", "state", strconv.FormatFloat(n.p13, 'f', 2, 64)} //nolint:gomnd + netemCmd = append(netemCmd, + strconv.FormatFloat(n.p31, 'f', 2, 64), //nolint:gomnd + strconv.FormatFloat(n.p32, 'f', 2, 64), //nolint:gomnd + strconv.FormatFloat(n.p23, 'f', 2, 64), //nolint:gomnd + strconv.FormatFloat(n.p14, 'f', 2, 64)) //nolint:gomnd // run netem loss command for selected containers var wg sync.WaitGroup @@ -198,7 +127,7 @@ func (n *LossStateCommand) Run(ctx context.Context, random bool) error { }() // scan through all errors in goroutines - for _, err := range errs { + for _, err = range errs { // take first found error if err != nil { return errors.Wrap(err, "failed to add packet loss (4-state Markov model) for one or more containers") diff --git a/pkg/chaos/netem/netem.go b/pkg/chaos/netem/netem.go index 90b19819..1f19292a 100644 --- a/pkg/chaos/netem/netem.go +++ b/pkg/chaos/netem/netem.go @@ -5,10 +5,67 @@ import ( "net" "time" + "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/container" + "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) +// `netem` base command +type netemCommand struct { + client container.Client + names []string + pattern string + labels []string + iface string + ips []*net.IPNet + sports []string + dports []string + duration time.Duration + image string + pull bool + limit int + dryRun bool +} + +// Params common params for netem traffic shaping command +type Params struct { + // network interface + Iface string + // target IP addresses + Ips []*net.IPNet + // egress port list (comma separated) + Sports []string + // ingress port list (comma separated) + Dports []string + // duration of the traffic shaping + Duration time.Duration + // image name + Image string + // force pull image + Pull bool + // limit the number of target containers + Limit int +} + +func newNetemCommand(client container.Client, gparams *chaos.GlobalParams, params *Params) netemCommand { + return netemCommand{ + client: client, + names: gparams.Names, + pattern: gparams.Pattern, + labels: gparams.Labels, + dryRun: gparams.DryRun, + iface: params.Iface, + ips: params.Ips, + sports: params.Sports, + dports: params.Dports, + duration: params.Duration, + image: params.Image, + pull: params.Pull, + limit: params.Limit, + } +} + // run network emulation command, stop netem on timeout or abort func runNetem(ctx context.Context, client container.Client, c *container.Container, netInterface string, cmd []string, ips []*net.IPNet, sports, dports []string, duration time.Duration, tcimage string, pull, dryRun bool) error { log.WithFields(log.Fields{ @@ -23,10 +80,9 @@ func runNetem(ctx context.Context, client container.Client, c *container.Contain "tc-image": tcimage, "pull": pull, }).Debug("running netem command") - var err error - err = client.NetemContainer(ctx, c, netInterface, cmd, ips, sports, dports, duration, tcimage, pull, dryRun) + err := client.NetemContainer(ctx, c, netInterface, cmd, ips, sports, dports, duration, tcimage, pull, dryRun) if err != nil { - return err + return errors.Wrap(err, "netem failed") } // create new context with timeout for canceling @@ -46,6 +102,9 @@ func runNetem(ctx context.Context, client container.Client, c *container.Contain }).Debug("stopping netem command on abort") // use different context to stop netem since parent context is canceled err = client.StopNetemContainer(context.Background(), c, netInterface, ips, sports, dports, tcimage, pull, dryRun) + if err != nil { + return errors.Wrap(err, "failed to stop netem container") + } case <-stopCtx.Done(): log.WithFields(log.Fields{ "id": c.ID(), @@ -58,6 +117,9 @@ func runNetem(ctx context.Context, client container.Client, c *container.Contain }).Debug("stopping netem command on timout") // use parent context to stop netem in container err = client.StopNetemContainer(context.Background(), c, netInterface, ips, sports, dports, tcimage, pull, dryRun) + if err != nil { + return errors.Wrap(err, "failed to stop netem container") + } } - return err + return nil } diff --git a/pkg/chaos/netem/netem_test.go b/pkg/chaos/netem/netem_test.go index d8f8c7ea..ff000780 100644 --- a/pkg/chaos/netem/netem_test.go +++ b/pkg/chaos/netem/netem_test.go @@ -11,7 +11,6 @@ import ( "github.com/stretchr/testify/mock" ) -//nolint:funlen func Test_runNetem(t *testing.T) { type errs struct { startErr bool @@ -39,10 +38,10 @@ func Test_runNetem(t *testing.T) { { name: "netem with duration", args: args{ - container: container.NewContainer( - container.DetailsResponse(container.AsMap("Name", "c1")), - container.ImageDetailsResponse(container.AsMap()), - ), + container: &container.Container{ + ContainerInfo: container.DetailsResponse(container.AsMap("Name", "c1")), + ImageInfo: container.ImageDetailsResponse(container.AsMap()), + }, netInterface: "testIface", cmd: []string{"test", "--test"}, ips: []*net.IPNet{{IP: net.IP{10, 10, 10, 10}}}, @@ -55,10 +54,10 @@ func Test_runNetem(t *testing.T) { { name: "netem with CIDR IP", args: args{ - container: container.NewContainer( - container.DetailsResponse(container.AsMap("Name", "c1")), - container.ImageDetailsResponse(container.AsMap()), - ), + container: &container.Container{ + ContainerInfo: container.DetailsResponse(container.AsMap("Name", "c1")), + ImageInfo: container.ImageDetailsResponse(container.AsMap()), + }, netInterface: "testIface", cmd: []string{"test", "--test"}, ips: []*net.IPNet{{IP: net.IP{10, 10, 10, 10}, Mask: net.IPMask{0, 0, 255, 255}}}, @@ -69,10 +68,10 @@ func Test_runNetem(t *testing.T) { { name: "netem with abort", args: args{ - container: container.NewContainer( - container.DetailsResponse(container.AsMap("Name", "c1")), - container.ImageDetailsResponse(container.AsMap()), - ), + container: &container.Container{ + ContainerInfo: container.DetailsResponse(container.AsMap("Name", "c1")), + ImageInfo: container.ImageDetailsResponse(container.AsMap()), + }, netInterface: "testIface", cmd: []string{"test", "--test"}, ips: []*net.IPNet{{IP: net.IP{10, 10, 10, 10}}}, @@ -84,10 +83,10 @@ func Test_runNetem(t *testing.T) { { name: "netem error in NetemContainer", args: args{ - container: container.NewContainer( - container.DetailsResponse(container.AsMap("Name", "c1")), - container.ImageDetailsResponse(container.AsMap()), - ), + container: &container.Container{ + ContainerInfo: container.DetailsResponse(container.AsMap("Name", "c1")), + ImageInfo: container.ImageDetailsResponse(container.AsMap()), + }, netInterface: "testIface", cmd: []string{"test", "--test"}, ips: []*net.IPNet{{IP: net.IP{10, 10, 10, 10}}}, @@ -100,10 +99,10 @@ func Test_runNetem(t *testing.T) { { name: "netem error in StopNetemContainer", args: args{ - container: container.NewContainer( - container.DetailsResponse(container.AsMap("Name", "c1")), - container.ImageDetailsResponse(container.AsMap()), - ), + container: &container.Container{ + ContainerInfo: container.DetailsResponse(container.AsMap("Name", "c1")), + ImageInfo: container.ImageDetailsResponse(container.AsMap()), + }, netInterface: "testIface", cmd: []string{"test", "--test"}, ips: []*net.IPNet{{IP: net.IP{10, 10, 10, 10}}}, diff --git a/pkg/chaos/netem/rate.go b/pkg/chaos/netem/rate.go index 9922c16f..9a0e0b71 100644 --- a/pkg/chaos/netem/rate.go +++ b/pkg/chaos/netem/rate.go @@ -2,17 +2,13 @@ package netem import ( "context" - "net" "regexp" "strconv" "sync" - "time" "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/container" - "github.com/alexei-led/pumba/pkg/util" "github.com/pkg/errors" - log "github.com/sirupsen/logrus" ) @@ -26,89 +22,31 @@ func parseRate(rate string) (string, error) { return rate, nil } -// RateCommand `netem rate` command -type RateCommand struct { - client container.Client - names []string - pattern string - labels []string - iface string - ips []*net.IPNet - sports []string - dports []string - duration time.Duration +// `netem rate` command +type rateCommand struct { + netemCommand rate string packetOverhead int cellSize int cellOverhead int - image string - pull bool - limit int - dryRun bool } // NewRateCommand create new netem rate command func NewRateCommand(client container.Client, - names []string, // containers - pattern string, // re2 regex pattern - labels []string, // filter by labels - iface string, // network interface - ipsList []string, // list of target ips - sportsList, // list of comma separated target sports - dportsList, // list of comma separated target dports - durationStr, // chaos duration - intervalStr, // repeatable chaos interval + globalParams *chaos.GlobalParams, + netemParams *Params, rate string, // delay outgoing packets; in common units packetOverhead, // per packet overhead; in bytes cellSize, // cell size of the simulated link layer scheme cellOverhead int, // per cell overhead; in bytes - image string, // traffic control image - pull bool, // pull tc image - limit int, // limit chaos to containers - dryRun bool, // dry-run do not netem just log ) (chaos.Command, error) { - // get interval - interval, err := util.GetIntervalValue(intervalStr) - if err != nil { - return nil, err - } - // get duration - duration, err := util.GetDurationValue(durationStr, interval) - if err != nil { - return nil, err - } - // protect from Command Injection, using Regexp - reInterface := regexp.MustCompile("[a-zA-Z][a-zA-Z0-9\\.:_-]*") - validIface := reInterface.FindString(iface) - if iface != validIface { - return nil, errors.Errorf("bad network interface name: must match '%s'", reInterface.String()) - } - // validate ips - var ips []*net.IPNet - for _, str := range ipsList { - ip, e := util.ParseCIDR(str) - if e != nil { - return nil, e - } - ips = append(ips, ip) - } - // validate sports - sports, err := util.GetPorts(sportsList) - if err != nil { - return nil, err - } - // validate dports - dports, err := util.GetPorts(dportsList) - if err != nil { - return nil, err - } // validate target egress rate if rate == "" { return nil, errors.New("undefined rate limit") } - rate, err = parseRate(rate) + rate, err := parseRate(rate) if err != nil { - return nil, err + return nil, errors.Wrap(err, "invalid rate") } // validate cell size @@ -116,29 +54,17 @@ func NewRateCommand(client container.Client, return nil, errors.New("invalid cell size: must be a non-negative integer") } - return &RateCommand{ - client: client, - names: names, - pattern: pattern, - labels: labels, - iface: iface, - ips: ips, - sports: sports, - dports: dports, - duration: duration, + return &rateCommand{ + netemCommand: newNetemCommand(client, globalParams, netemParams), rate: rate, packetOverhead: packetOverhead, cellSize: cellSize, cellOverhead: cellOverhead, - image: image, - pull: pull, - limit: limit, - dryRun: dryRun, }, nil } // Run netem rate command -func (n *RateCommand) Run(ctx context.Context, random bool) error { +func (n *rateCommand) Run(ctx context.Context, random bool) error { log.Debug("setting network rate to all matching containers") log.WithFields(log.Fields{ "names": n.names, @@ -149,7 +75,7 @@ func (n *RateCommand) Run(ctx context.Context, random bool) error { }).Debug("listing matching containers") containers, err := container.ListNContainers(ctx, n.client, n.names, n.pattern, n.labels, n.limit) if err != nil { - return err + return errors.Wrap(err, "error listing containers") } if len(containers) == 0 { log.Warning("no containers found") @@ -207,7 +133,7 @@ func (n *RateCommand) Run(ctx context.Context, random bool) error { }() // scan through all errors in goroutines - for _, err := range errs { + for _, err = range errs { // take first found error if err != nil { return errors.Wrap(err, "failed to set network rate for one or more containers") diff --git a/pkg/chaos/stress/cmd/stress.go b/pkg/chaos/stress/cmd/stress.go index 88504996..0699da82 100644 --- a/pkg/chaos/stress/cmd/stress.go +++ b/pkg/chaos/stress/cmd/stress.go @@ -4,10 +4,10 @@ import ( "context" "fmt" - "github.com/urfave/cli" - "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/chaos/stress" + "github.com/pkg/errors" + "github.com/urfave/cli" ) type stressContext struct { @@ -46,35 +46,32 @@ func NewStressCLICommand(ctx context.Context) *cli.Command { } } -// stress Command +// stress stressNg func (cmd *stressContext) stress(c *cli.Context) error { - // get random - random := c.GlobalBool("random") - // get labels - labels := c.GlobalStringSlice("label") - // get dry-run mode - dryRun := c.GlobalBool("dry-run") - // get skip error flag - skipError := c.GlobalBool("skip-error") - // get interval - interval := c.GlobalString("interval") - // get names or pattern - names, pattern := chaos.GetNamesOrPattern(c) + // parse common chaos flags + globalParams, err := chaos.ParseGlobalParams(c) + if err != nil { + return errors.Wrap(err, "error parsing global parameters") + } // get limit for number of containers to kill limit := c.Int("limit") // get stress-ng stressors stressors := c.String("stressors") // get stress duration - duration := c.String("duration") + duration := c.Duration("duration") + if duration == 0 { + return errors.New("unset or invalid duration value") + } // get stress-ng image image := c.String("stress-image") // get pull tc image flag pull := c.BoolT("pull-image") // init stress command - stressCommand, err := stress.NewStressCommand(chaos.DockerClient, names, pattern, labels, image, pull, stressors, interval, duration, limit, dryRun) + stressCommand := stress.NewStressCommand(chaos.DockerClient, globalParams, image, pull, stressors, duration, limit) + // run stress command + err = chaos.RunChaosCommand(cmd.context, stressCommand, globalParams) if err != nil { - return err + return errors.Wrap(err, "error running stress command") } - // run stress command - return chaos.RunChaosCommand(cmd.context, stressCommand, interval, random, skipError) + return nil } diff --git a/pkg/chaos/stress/stress.go b/pkg/chaos/stress/stress.go index b64fc1dd..a239ffdd 100644 --- a/pkg/chaos/stress/stress.go +++ b/pkg/chaos/stress/stress.go @@ -7,14 +7,13 @@ import ( "github.com/alexei-led/pumba/pkg/chaos" "github.com/alexei-led/pumba/pkg/container" - "github.com/alexei-led/pumba/pkg/util" "github.com/pkg/errors" log "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" ) -// Command `stress-ng` command -type Command struct { +// `stress-ng` command +type stressCommand struct { client container.Client names []string pattern string @@ -31,24 +30,25 @@ const ( defaultStopTimeout = 5 * time.Second ) -// NewStressCommand create new Kill Command instance -func NewStressCommand(client container.Client, names []string, pattern string, labels []string, image string, pull bool, stressors, interval, duration string, limit int, dryRun bool) (chaos.Command, error) { - // get interval - i, err := util.GetIntervalValue(interval) - if err != nil { - return nil, err - } - // get duration - d, err := util.GetDurationValue(duration, i) - if err != nil { - return nil, err +// NewStressCommand create new Kill stressCommand instance +func NewStressCommand(client container.Client, globalParams *chaos.GlobalParams, image string, pull bool, stressors string, duration time.Duration, limit int) chaos.Command { + stress := &stressCommand{ + client: client, + names: globalParams.Names, + pattern: globalParams.Pattern, + labels: globalParams.Labels, + image: image, + pull: pull, + stressors: strings.Fields(stressors), + duration: duration, + limit: limit, + dryRun: globalParams.DryRun, } - stress := &Command{client, names, pattern, labels, image, pull, strings.Fields(stressors), d, limit, dryRun} - return stress, nil + return stress } // Run stress command -func (s *Command) Run(ctx context.Context, random bool) error { +func (s *stressCommand) Run(ctx context.Context, random bool) error { log.Debug("stress testing all matching containers") log.WithFields(log.Fields{ "names": s.names, @@ -61,7 +61,7 @@ func (s *Command) Run(ctx context.Context, random bool) error { }).Debug("listing matching containers") containers, err := container.ListNContainers(ctx, s.client, s.names, s.pattern, s.labels, s.limit) if err != nil { - return err + return errors.Wrap(err, "error listing containers") } if len(containers) == 0 { log.Warning("no containers to stress test") @@ -90,7 +90,7 @@ func (s *Command) Run(ctx context.Context, random bool) error { return nil } -func (s *Command) stressContainer(ctx context.Context, c *container.Container) error { +func (s *stressCommand) stressContainer(ctx context.Context, c *container.Container) error { log.WithFields(log.Fields{ "container": c.ID(), "duration": s.duration, @@ -100,7 +100,7 @@ func (s *Command) stressContainer(ctx context.Context, c *container.Container) e }).Debug("stress testing container for duration") stress, output, outerr, err := s.client.StressContainer(ctx, c, s.stressors, s.image, s.pull, s.duration, s.dryRun) if err != nil { - return err + return errors.Wrap(err, "stress test failed") } select { case out := <-output: diff --git a/pkg/container/client.go b/pkg/container/client.go index 8e3ed800..71db16e3 100644 --- a/pkg/container/client.go +++ b/pkg/container/client.go @@ -12,15 +12,14 @@ import ( "strings" "time" - "github.com/pkg/errors" - log "github.com/sirupsen/logrus" - - types "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types" ctypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/mount" dockerapi "github.com/docker/docker/client" "github.com/docker/go-connections/nat" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" ) const ( @@ -38,7 +37,7 @@ type Client interface { StopContainer(context.Context, *Container, int, bool) error KillContainer(context.Context, *Container, string, bool) error ExecContainer(context.Context, *Container, string, bool) error - RestartContainer(context.Context, *Container, time.Duration, time.Duration, bool) error + RestartContainer(context.Context, *Container, time.Duration, bool) error RemoveContainer(context.Context, *Container, bool, bool, bool, bool) error NetemContainer(context.Context, *Container, string, []string, []*net.IPNet, []string, []string, time.Duration, string, bool, bool) error StopNetemContainer(context.Context, *Container, string, []*net.IPNet, []string, []string, string, bool, bool) error @@ -49,8 +48,7 @@ type Client interface { StopContainerWithID(context.Context, string, time.Duration, bool) error } -// ImagePullResponse - response from ImagePull -type ImagePullResponse struct { +type imagePullResponse struct { Status string `json:"status"` Error string `json:"error"` Progress string `json:"progress"` @@ -60,8 +58,7 @@ type ImagePullResponse struct { } `json:"progressDetail"` } -// NewClient returns a new Client instance which can be used to interact with -// the Docker API. +// NewClient returns a new Client instance which can be used to interact with the Docker API. func NewClient(dockerHost string, tlsConfig *tls.Config) (Client, error) { httpClient, err := HTTPClient(dockerHost, tlsConfig) if err != nil { @@ -70,7 +67,7 @@ func NewClient(dockerHost string, tlsConfig *tls.Config) (Client, error) { apiClient, err := dockerapi.NewClientWithOpts(dockerapi.WithHost(dockerHost), dockerapi.WithHTTPClient(httpClient), dockerapi.WithAPIVersionNegotiation()) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to create docker client") } return dockerClient{containerAPI: apiClient, imageAPI: apiClient}, nil @@ -81,6 +78,7 @@ type dockerClient struct { imageAPI dockerapi.ImageAPIClient } +// ListContainers returns a list of containers that match the given filter func (client dockerClient) ListContainers(ctx context.Context, fn FilterFunc, opts ListOpts) ([]*Container, error) { filterArgs := filters.NewArgs() for _, label := range opts.Labels { @@ -110,7 +108,7 @@ func (client dockerClient) listContainers(ctx context.Context, fn FilterFunc, op if err != nil { return nil, errors.Wrap(err, "failed to inspect container image") } - c := &Container{containerInfo: containerInfo, imageInfo: imageInfo} + c := &Container{ContainerInfo: containerInfo, ImageInfo: imageInfo} if fn(c) { cs = append(cs, c) } @@ -118,6 +116,7 @@ func (client dockerClient) listContainers(ctx context.Context, fn FilterFunc, op return cs, nil } +// KillContainer kills a container with the given signal func (client dockerClient) KillContainer(ctx context.Context, c *Container, signal string, dryrun bool) error { log.WithFields(log.Fields{ "name": c.Name(), @@ -126,11 +125,15 @@ func (client dockerClient) KillContainer(ctx context.Context, c *Container, sign "dryrun": dryrun, }).Info("killing container") if !dryrun { - return client.containerAPI.ContainerKill(ctx, c.ID(), signal) + err := client.containerAPI.ContainerKill(ctx, c.ID(), signal) + if err != nil { + return errors.Wrap(err, "failed to kill container") + } } return nil } +// ExecContainer executes a command in a container func (client dockerClient) ExecContainer(ctx context.Context, c *Container, command string, dryrun bool) error { log.WithFields(log.Fields{ "name": c.Name(), @@ -158,7 +161,7 @@ func (client dockerClient) ExecContainer(ctx context.Context, c *Container, comm return errors.Wrap(err, "exec attach failed") } - if err := client.containerAPI.ContainerExecStart( + if err = client.containerAPI.ContainerExecStart( ctx, createRes.ID, types.ExecStartCheck{}, ); err != nil { return errors.Wrap(err, "exec start failed") @@ -186,7 +189,8 @@ func (client dockerClient) ExecContainer(ctx context.Context, c *Container, comm return nil } -func (client dockerClient) RestartContainer(ctx context.Context, c *Container, timeout time.Duration, delay time.Duration, dryrun bool) error { +// RestartContainer restarts a container +func (client dockerClient) RestartContainer(ctx context.Context, c *Container, timeout time.Duration, dryrun bool) error { log.WithFields(log.Fields{ "name": c.Name(), "id": c.ID(), @@ -194,23 +198,14 @@ func (client dockerClient) RestartContainer(ctx context.Context, c *Container, t "dryrun": dryrun, }).Info("restart container") if !dryrun { - if err := client.containerAPI.ContainerStop( - ctx, c.ID(), &timeout, - ); err != nil { - return errors.Wrap(err, "restart stop failed") - } - - time.Sleep(delay) - - if err := client.containerAPI.ContainerStart( - ctx, c.ID(), types.ContainerStartOptions{}, - ); err != nil { - return errors.Wrap(err, "restart start failed") + if err := client.containerAPI.ContainerRestart(ctx, c.ID(), &timeout); err != nil { + return errors.Wrap(err, "failed to restart container") } } return nil } +// StopContainer stops a container func (client dockerClient) StopContainer(ctx context.Context, c *Container, timeout int, dryrun bool) error { signal := c.StopSignal() if signal == "" { @@ -225,7 +220,7 @@ func (client dockerClient) StopContainer(ctx context.Context, c *Container, time }).Info("stopping container") if !dryrun { if err := client.containerAPI.ContainerKill(ctx, c.ID(), signal); err != nil { - return err + return errors.Wrap(err, "failed to kill container") } // Wait for container to exit, but proceed anyway after the timeout elapses @@ -254,6 +249,7 @@ func (client dockerClient) StopContainer(ctx context.Context, c *Container, time return nil } +// StopContainerWithID stops a container with a timeout func (client dockerClient) StopContainerWithID(ctx context.Context, containerID string, timeout time.Duration, dryrun bool) error { log.WithFields(log.Fields{ "id": containerID, @@ -261,11 +257,15 @@ func (client dockerClient) StopContainerWithID(ctx context.Context, containerID "dryrun": dryrun, }).Info("stopping container") if !dryrun { - return client.containerAPI.ContainerStop(ctx, containerID, &timeout) + err := client.containerAPI.ContainerStop(ctx, containerID, &timeout) + if err != nil { + return errors.Wrap(err, "failed to stop container") + } } return nil } +// StartContainer starts a container func (client dockerClient) StartContainer(ctx context.Context, c *Container, dryrun bool) error { log.WithFields(log.Fields{ "name": c.Name(), @@ -273,12 +273,16 @@ func (client dockerClient) StartContainer(ctx context.Context, c *Container, dry "dryrun": dryrun, }).Info("starting container") if !dryrun { - return client.containerAPI.ContainerStart(ctx, c.ID(), types.ContainerStartOptions{}) + err := client.containerAPI.ContainerStart(ctx, c.ID(), types.ContainerStartOptions{}) + if err != nil { + return errors.Wrap(err, "failed to start container") + } } return nil } +// RemoveContainer removes a container func (client dockerClient) RemoveContainer(ctx context.Context, c *Container, force, links, volumes, dryrun bool) error { log.WithFields(log.Fields{ "name": c.Name(), @@ -294,11 +298,15 @@ func (client dockerClient) RemoveContainer(ctx context.Context, c *Container, fo RemoveLinks: links, Force: force, } - return client.containerAPI.ContainerRemove(ctx, c.ID(), removeOpts) + err := client.containerAPI.ContainerRemove(ctx, c.ID(), removeOpts) + if err != nil { + return errors.Wrap(err, "failed to remove container") + } } return nil } +// NetemContainer injects sidecar netem container into the given container network namespace func (client dockerClient) NetemContainer(ctx context.Context, c *Container, netInterface string, netemCmd []string, ips []*net.IPNet, sports, dports []string, duration time.Duration, tcimage string, pull, dryrun bool) error { log.WithFields(log.Fields{ "name": c.Name(), @@ -318,6 +326,7 @@ func (client dockerClient) NetemContainer(ctx context.Context, c *Container, net return client.startNetemContainerIPFilter(ctx, c, netInterface, netemCmd, ips, sports, dports, tcimage, pull, dryrun) } +// StopNetemContainer stops the netem container injected into the given container network namespace func (client dockerClient) StopNetemContainer(ctx context.Context, c *Container, netInterface string, ip []*net.IPNet, sports, dports []string, tcimage string, pull, dryrun bool) error { log.WithFields(log.Fields{ "name": c.Name(), @@ -333,6 +342,7 @@ func (client dockerClient) StopNetemContainer(ctx context.Context, c *Container, return client.stopNetemContainer(ctx, c, netInterface, ip, sports, dports, tcimage, pull, dryrun) } +// PauseContainer pauses a container main process func (client dockerClient) PauseContainer(ctx context.Context, c *Container, dryrun bool) error { log.WithFields(log.Fields{ "name": c.Name(), @@ -340,11 +350,15 @@ func (client dockerClient) PauseContainer(ctx context.Context, c *Container, dry "dryrun": dryrun, }).Info("pausing container") if !dryrun { - return client.containerAPI.ContainerPause(ctx, c.ID()) + err := client.containerAPI.ContainerPause(ctx, c.ID()) + if err != nil { + return errors.Wrap(err, "failed to pause container") + } } return nil } +// UnpauseContainer unpauses a container main process func (client dockerClient) UnpauseContainer(ctx context.Context, c *Container, dryrun bool) error { log.WithFields(log.Fields{ "name": c.Name(), @@ -352,11 +366,15 @@ func (client dockerClient) UnpauseContainer(ctx context.Context, c *Container, d "dryrun": dryrun, }).Info("stop pausing container") if !dryrun { - return client.containerAPI.ContainerUnpause(ctx, c.ID()) + err := client.containerAPI.ContainerUnpause(ctx, c.ID()) + if err != nil { + return errors.Wrap(err, "failed to unpause container") + } } return nil } +// StressContainer starts stress test on a container (CPU, memory, network, io) func (client dockerClient) StressContainer(ctx context.Context, c *Container, stressors []string, image string, pull bool, duration time.Duration, dryrun bool) (string, <-chan string, <-chan error, error) { log.WithFields(log.Fields{ "name": c.Name(), @@ -454,6 +472,7 @@ func (client dockerClient) stopNetemContainer(ctx context.Context, c *Container, return nil } +//nolint:funlen func (client dockerClient) startNetemContainerIPFilter(ctx context.Context, c *Container, netInterface string, netemCmd []string, ips []*net.IPNet, sports []string, dports []string, tcimage string, pull bool, dryrun bool) error { log.WithFields(log.Fields{ @@ -610,10 +629,10 @@ func (client dockerClient) tcContainerCommand(ctx context.Context, target *Conta } defer events.Close() d := json.NewDecoder(events) - var pullResponse *ImagePullResponse + var pullResponse *imagePullResponse for { if err = d.Decode(&pullResponse); err != nil { - if err == io.EOF { + if errors.Is(err, io.EOF) { break } return errors.Wrap(err, "failed to decode docker pull response for tc-image") @@ -622,7 +641,7 @@ func (client dockerClient) tcContainerCommand(ctx context.Context, target *Conta } } log.WithField("image", config.Image).Debug("creating tc-container") - createResponse, err := client.containerAPI.ContainerCreate(ctx, &config, &hconfig, nil, "") + createResponse, err := client.containerAPI.ContainerCreate(ctx, &config, &hconfig, nil, nil, "") if err != nil { return errors.Wrap(err, "failed to create tc-container from tc-image") } @@ -634,6 +653,7 @@ func (client dockerClient) tcContainerCommand(ctx context.Context, target *Conta return nil } +//nolint:funlen // execute a stress-ng command in stress-ng Docker container in target container cgroup func (client dockerClient) stressContainerCommand(ctx context.Context, targetID string, stressors []string, image string, pull bool) (string, <-chan string, <-chan error, error) { log.WithFields(log.Fields{ @@ -688,10 +708,10 @@ func (client dockerClient) stressContainerCommand(ctx context.Context, targetID } defer events.Close() d := json.NewDecoder(events) - var pullResponse *ImagePullResponse + var pullResponse *imagePullResponse for { if err = d.Decode(&pullResponse); err != nil { - if err == io.EOF { + if errors.Is(err, io.EOF) { break } close(outerr) @@ -703,7 +723,7 @@ func (client dockerClient) stressContainerCommand(ctx context.Context, targetID } // create stress-ng container log.WithField("image", config.Image).Debug("creating stress-ng container") - createResponse, err := client.containerAPI.ContainerCreate(ctx, &config, &hconfig, nil, "") + createResponse, err := client.containerAPI.ContainerCreate(ctx, &config, &hconfig, nil, nil, "") if err != nil { close(outerr) close(output) @@ -765,7 +785,7 @@ func (client dockerClient) execOnContainer(ctx context.Context, c *Container, ex "privileged": privileged, }).Debug("executing command in container") // trim all spaces from cmd - execCmd = strings.Replace(execCmd, " ", "", -1) + execCmd = strings.ReplaceAll(execCmd, " ", "") // check if command exists inside target container checkExists := types.ExecConfig{ @@ -817,6 +837,9 @@ func (client dockerClient) execOnContainer(ctx context.Context, c *Container, ex } func (client dockerClient) waitForStop(ctx context.Context, c *Container, waitTime int) error { + // check status every 100 ms + const checkInterval = 100 * time.Microsecond + // timeout after waitTime seconds timeout := time.After(time.Duration(waitTime) * time.Second) log.WithFields(log.Fields{ "name": c.Name(), @@ -836,7 +859,6 @@ func (client dockerClient) waitForStop(ctx context.Context, c *Container, waitTi return nil } } - // check status every 100 ms - time.Sleep(100 * time.Microsecond) + time.Sleep(checkInterval) } } diff --git a/pkg/container/client_test.go b/pkg/container/client_test.go index e0de32c4..a8babbd8 100644 --- a/pkg/container/client_test.go +++ b/pkg/container/client_test.go @@ -16,6 +16,7 @@ import ( "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" "github.com/docker/go-connections/nat" + specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -50,8 +51,8 @@ func TestListContainers_Success(t *testing.T) { assert.NoError(t, err) assert.Len(t, containers, 1) - assert.Equal(t, containerDetails, containers[0].containerInfo) - assert.Equal(t, imageDetails, containers[0].imageInfo) + assert.Equal(t, containerDetails, containers[0].ContainerInfo) + assert.Equal(t, imageDetails, containers[0].ImageInfo) api.AssertExpectations(t) } @@ -130,7 +131,7 @@ func TestStopContainer_DefaultSuccess(t *testing.T) { "ID", "abc123", "Name", "foo", )) - c := &Container{containerInfo: containerDetails} + c := &Container{ContainerInfo: containerDetails} notRunningContainer := DetailsResponse(AsMap("Running", false)) api := NewMockEngine() @@ -146,7 +147,7 @@ func TestStopContainer_DefaultSuccess(t *testing.T) { func TestStopContainer_DryRun(t *testing.T) { c := &Container{ - containerInfo: DetailsResponse(AsMap( + ContainerInfo: DetailsResponse(AsMap( "ID", "abc123", "Name", "foo", )), @@ -172,7 +173,7 @@ func TestStopContainer_DryRun(t *testing.T) { func TestKillContainer_DefaultSuccess(t *testing.T) { c := &Container{ - containerInfo: DetailsResponse(AsMap( + ContainerInfo: DetailsResponse(AsMap( "ID", "abc123", "Name", "foo", )), @@ -190,7 +191,7 @@ func TestKillContainer_DefaultSuccess(t *testing.T) { func TestKillContainer_DryRun(t *testing.T) { c := &Container{ - containerInfo: DetailsResponse(AsMap( + ContainerInfo: DetailsResponse(AsMap( "ID", "abc123", "Name", "foo", )), @@ -208,7 +209,7 @@ func TestKillContainer_DryRun(t *testing.T) { func TestStopContainer_CustomSignalSuccess(t *testing.T) { c := &Container{ - containerInfo: DetailsResponse(AsMap( + ContainerInfo: DetailsResponse(AsMap( "ID", "abc123", "Name", "foo", "Labels", map[string]string{"com.gaiaadm.pumba.stop-signal": "SIGUSR1"}, @@ -230,7 +231,7 @@ func TestStopContainer_CustomSignalSuccess(t *testing.T) { func TestStopContainer_KillContainerError(t *testing.T) { c := &Container{ - containerInfo: DetailsResponse(AsMap( + ContainerInfo: DetailsResponse(AsMap( "ID", "abc123", "Name", "foo", )), @@ -243,13 +244,12 @@ func TestStopContainer_KillContainerError(t *testing.T) { err := client.StopContainer(context.TODO(), c, 1, false) assert.Error(t, err) - assert.EqualError(t, err, "oops") api.AssertExpectations(t) } func TestStopContainer_2ndKillContainerError(t *testing.T) { c := &Container{ - containerInfo: DetailsResponse(AsMap( + ContainerInfo: DetailsResponse(AsMap( "ID", "abc123", "Name", "foo", )), @@ -270,7 +270,7 @@ func TestStopContainer_2ndKillContainerError(t *testing.T) { func TestRemoveContainer_Success(t *testing.T) { c := &Container{ - containerInfo: DetailsResponse(AsMap("ID", "abc123")), + ContainerInfo: DetailsResponse(AsMap("ID", "abc123")), } engineClient := NewMockEngine() @@ -286,7 +286,7 @@ func TestRemoveContainer_Success(t *testing.T) { func TestRemoveContainer_DryRun(t *testing.T) { c := &Container{ - containerInfo: DetailsResponse(AsMap("ID", "abc123")), + ContainerInfo: DetailsResponse(AsMap("ID", "abc123")), } engineClient := NewMockEngine() @@ -302,7 +302,7 @@ func TestRemoveContainer_DryRun(t *testing.T) { func TestPauseContainer_Success(t *testing.T) { c := &Container{ - containerInfo: DetailsResponse(AsMap("ID", "abc123")), + ContainerInfo: DetailsResponse(AsMap("ID", "abc123")), } engineClient := NewMockEngine() engineClient.On("ContainerPause", mock.Anything, "abc123").Return(nil) @@ -316,7 +316,7 @@ func TestPauseContainer_Success(t *testing.T) { func TestUnpauseContainer_Success(t *testing.T) { c := &Container{ - containerInfo: DetailsResponse(AsMap("ID", "abc123")), + ContainerInfo: DetailsResponse(AsMap("ID", "abc123")), } engineClient := NewMockEngine() engineClient.On("ContainerUnpause", mock.Anything, "abc123").Return(nil) @@ -330,7 +330,7 @@ func TestUnpauseContainer_Success(t *testing.T) { func TestPauseContainer_DryRun(t *testing.T) { c := &Container{ - containerInfo: DetailsResponse(AsMap("ID", "abc123")), + ContainerInfo: DetailsResponse(AsMap("ID", "abc123")), } engineClient := NewMockEngine() @@ -343,7 +343,7 @@ func TestPauseContainer_DryRun(t *testing.T) { func TestPauseContainer_PauseError(t *testing.T) { c := &Container{ - containerInfo: DetailsResponse(AsMap("ID", "abc123")), + ContainerInfo: DetailsResponse(AsMap("ID", "abc123")), } engineClient := NewMockEngine() engineClient.On("ContainerPause", mock.Anything, "abc123").Return(errors.New("pause")) @@ -352,13 +352,12 @@ func TestPauseContainer_PauseError(t *testing.T) { err := client.PauseContainer(context.TODO(), c, false) assert.Error(t, err) - assert.EqualError(t, err, "pause") engineClient.AssertExpectations(t) } func TestPauseContainer_UnpauseError(t *testing.T) { c := &Container{ - containerInfo: DetailsResponse(AsMap("ID", "abc123")), + ContainerInfo: DetailsResponse(AsMap("ID", "abc123")), } engineClient := NewMockEngine() engineClient.On("ContainerUnpause", mock.Anything, "abc123").Return(errors.New("unpause")) @@ -367,13 +366,12 @@ func TestPauseContainer_UnpauseError(t *testing.T) { err := client.UnpauseContainer(context.TODO(), c, false) assert.Error(t, err) - assert.EqualError(t, err, "unpause") engineClient.AssertExpectations(t) } func TestNetemContainer_Success(t *testing.T) { c := &Container{ - containerInfo: DetailsResponse(AsMap("ID", "abc123")), + ContainerInfo: DetailsResponse(AsMap("ID", "abc123")), } engineClient := NewMockEngine() @@ -397,7 +395,7 @@ func TestNetemContainer_Success(t *testing.T) { func TestStopNetemContainer_Success(t *testing.T) { c := &Container{ - containerInfo: DetailsResponse(AsMap("ID", "abc123")), + ContainerInfo: DetailsResponse(AsMap("ID", "abc123")), } ctx := mock.Anything @@ -422,7 +420,7 @@ func TestStopNetemContainer_Success(t *testing.T) { func TestNetemContainer_DryRun(t *testing.T) { c := &Container{ - containerInfo: DetailsResponse(AsMap("ID", "abc123")), + ContainerInfo: DetailsResponse(AsMap("ID", "abc123")), } engineClient := NewMockEngine() @@ -436,7 +434,7 @@ func TestNetemContainer_DryRun(t *testing.T) { func TestNetemContainerIPFilter_Success(t *testing.T) { c := &Container{ - containerInfo: DetailsResponse(AsMap("ID", "abc123")), + ContainerInfo: DetailsResponse(AsMap("ID", "abc123")), } ctx := mock.Anything @@ -482,7 +480,7 @@ func TestNetemContainerIPFilter_Success(t *testing.T) { func TestNetemContainerSportFilter_Success(t *testing.T) { c := &Container{ - containerInfo: DetailsResponse(AsMap("ID", "abc123")), + ContainerInfo: DetailsResponse(AsMap("ID", "abc123")), } ctx := mock.Anything @@ -528,7 +526,7 @@ func TestNetemContainerSportFilter_Success(t *testing.T) { func TestNetemContainerDportFilter_Success(t *testing.T) { c := &Container{ - containerInfo: DetailsResponse(AsMap("ID", "abc123")), + ContainerInfo: DetailsResponse(AsMap("ID", "abc123")), } ctx := mock.Anything @@ -574,7 +572,7 @@ func TestNetemContainerDportFilter_Success(t *testing.T) { func Test_tcContainerCommand(t *testing.T) { c := &Container{ - containerInfo: DetailsResponse(AsMap("ID", "targetID")), + ContainerInfo: DetailsResponse(AsMap("ID", "targetID")), } config := container.Config{ @@ -598,7 +596,7 @@ func Test_tcContainerCommand(t *testing.T) { DNSSearch: []string{}, } // pull response - pullResponse := ImagePullResponse{ + pullResponse := imagePullResponse{ Status: "ok", Error: "no error", Progress: "done", @@ -619,7 +617,7 @@ func Test_tcContainerCommand(t *testing.T) { // pull image engineClient.On("ImagePull", ctx, config.Image, types.ImagePullOptions{}).Return(ioutil.NopCloser(readerResponse), nil) // create container - engineClient.On("ContainerCreate", ctx, &config, &hconfig, (*network.NetworkingConfig)(nil), "").Return(container.ContainerCreateCreatedBody{ID: "tcID"}, nil) + engineClient.On("ContainerCreate", ctx, &config, &hconfig, (*network.NetworkingConfig)(nil), (*specs.Platform)(nil), "").Return(container.ContainerCreateCreatedBody{ID: "tcID"}, nil) // start container engineClient.On("ContainerStart", ctx, "tcID", types.ContainerStartOptions{}).Return(nil) @@ -635,7 +633,7 @@ func TestStartContainer_DefaultSuccess(t *testing.T) { "ID", "abc123", "Name", "foo", )) - c := &Container{containerInfo: containerDetails} + c := &Container{ContainerInfo: containerDetails} api := NewMockEngine() api.On("ContainerStart", mock.Anything, "abc123", types.ContainerStartOptions{}).Return(nil) @@ -649,7 +647,7 @@ func TestStartContainer_DefaultSuccess(t *testing.T) { func TestStartContainer_DryRun(t *testing.T) { c := &Container{ - containerInfo: DetailsResponse(AsMap( + ContainerInfo: DetailsResponse(AsMap( "ID", "abc123", "Name", "foo", )), @@ -665,7 +663,6 @@ func TestStartContainer_DryRun(t *testing.T) { api.AssertNotCalled(t, "ContainerStart", mock.Anything, "abc123", types.ContainerStartOptions{}) } -//nolint:funlen func Test_dockerClient_execOnContainer(t *testing.T) { type args struct { c *Container @@ -683,7 +680,7 @@ func Test_dockerClient_execOnContainer(t *testing.T) { { name: "run non-privileged command with args", args: args{ - c: &Container{containerInfo: DetailsResponse(AsMap("ID", "abc123"))}, + c: &Container{ContainerInfo: DetailsResponse(AsMap("ID", "abc123"))}, ctx: context.TODO(), execCmd: "test-app", execArgs: []string{"one", "two", "three"}, @@ -704,7 +701,7 @@ func Test_dockerClient_execOnContainer(t *testing.T) { { name: "run privileged command with args", args: args{ - c: &Container{containerInfo: DetailsResponse(AsMap("ID", "abc123"))}, + c: &Container{ContainerInfo: DetailsResponse(AsMap("ID", "abc123"))}, ctx: context.TODO(), execCmd: "test-app", execArgs: []string{"one", "two", "three"}, @@ -726,7 +723,7 @@ func Test_dockerClient_execOnContainer(t *testing.T) { { name: "fail to find command", args: args{ - c: &Container{containerInfo: DetailsResponse(AsMap("ID", "abc123"))}, + c: &Container{ContainerInfo: DetailsResponse(AsMap("ID", "abc123"))}, ctx: context.TODO(), execCmd: "test-app", execArgs: []string{"one", "two", "three"}, @@ -743,7 +740,7 @@ func Test_dockerClient_execOnContainer(t *testing.T) { { name: "fail to ContainerExecCreate 'which'", args: args{ - c: &Container{containerInfo: DetailsResponse(AsMap("ID", "abc123"))}, + c: &Container{ContainerInfo: DetailsResponse(AsMap("ID", "abc123"))}, ctx: context.TODO(), execCmd: "test-app", }, @@ -756,7 +753,7 @@ func Test_dockerClient_execOnContainer(t *testing.T) { { name: "fail to ContainerExecStart 'which'", args: args{ - c: &Container{containerInfo: DetailsResponse(AsMap("ID", "abc123"))}, + c: &Container{ContainerInfo: DetailsResponse(AsMap("ID", "abc123"))}, ctx: context.TODO(), execCmd: "test-app", }, @@ -770,7 +767,7 @@ func Test_dockerClient_execOnContainer(t *testing.T) { { name: "fail to ContainerExecInspect 'which'", args: args{ - c: &Container{containerInfo: DetailsResponse(AsMap("ID", "abc123"))}, + c: &Container{ContainerInfo: DetailsResponse(AsMap("ID", "abc123"))}, ctx: context.TODO(), execCmd: "test-app", }, @@ -785,7 +782,7 @@ func Test_dockerClient_execOnContainer(t *testing.T) { { name: "fail to ContainerExecCreate 'test-app'", args: args{ - c: &Container{containerInfo: DetailsResponse(AsMap("ID", "abc123"))}, + c: &Container{ContainerInfo: DetailsResponse(AsMap("ID", "abc123"))}, ctx: context.TODO(), execCmd: "test-app", }, @@ -803,7 +800,7 @@ func Test_dockerClient_execOnContainer(t *testing.T) { { name: "fail to ContainerExecStart 'test-app'", args: args{ - c: &Container{containerInfo: DetailsResponse(AsMap("ID", "abc123"))}, + c: &Container{ContainerInfo: DetailsResponse(AsMap("ID", "abc123"))}, ctx: context.TODO(), execCmd: "test-app", }, @@ -827,7 +824,7 @@ func Test_dockerClient_execOnContainer(t *testing.T) { containerAPI: mockClient, } // init mock engine - tt.mockInit(tt.args.ctx, mockClient, tt.args.c.containerInfo.ID, tt.args.execCmd, tt.args.execArgs) + tt.mockInit(tt.args.ctx, mockClient, tt.args.c.ContainerInfo.ID, tt.args.execCmd, tt.args.execArgs) err := client.execOnContainer(tt.args.ctx, tt.args.c, tt.args.execCmd, tt.args.execArgs, tt.args.privileged) if (err != nil) != tt.wantErr { t.Errorf("dockerClient.execOnContainer() error = %v, wantErr %v", err, tt.wantErr) @@ -838,7 +835,6 @@ func Test_dockerClient_execOnContainer(t *testing.T) { } } -//nolint:funlen func Test_dockerClient_stressContainerCommand(t *testing.T) { type args struct { ctx context.Context @@ -867,7 +863,7 @@ func Test_dockerClient_stressContainerCommand(t *testing.T) { }, mockInit: func(ctx context.Context, engine *mocks.APIClient, conn *mockConn, targetID string, stressors []string, image string, pool bool) { // pull response - pullResponse := ImagePullResponse{ + pullResponse := imagePullResponse{ Status: "ok", Error: "no error", Progress: "done", @@ -882,7 +878,7 @@ func Test_dockerClient_stressContainerCommand(t *testing.T) { pullResponseByte, _ := json.Marshal(pullResponse) readerResponse := bytes.NewReader(pullResponseByte) engine.On("ImagePull", ctx, image, types.ImagePullOptions{}).Return(ioutil.NopCloser(readerResponse), nil) - engine.On("ContainerCreate", ctx, mock.Anything, mock.Anything, mock.Anything, "").Return(container.ContainerCreateCreatedBody{ID: "000"}, nil) + engine.On("ContainerCreate", ctx, mock.Anything, mock.Anything, mock.Anything, mock.Anything, "").Return(container.ContainerCreateCreatedBody{ID: "000"}, nil) engine.On("ContainerAttach", ctx, "000", mock.Anything).Return(types.HijackedResponse{ Conn: conn, Reader: bufio.NewReader(strings.NewReader("stress completed")), @@ -917,7 +913,7 @@ func Test_dockerClient_stressContainerCommand(t *testing.T) { ctx: context.TODO(), }, mockInit: func(ctx context.Context, engine *mocks.APIClient, conn *mockConn, targetID string, stressors []string, image string, pool bool) { - engine.On("ContainerCreate", ctx, mock.Anything, mock.Anything, mock.Anything, "").Return(container.ContainerCreateCreatedBody{ID: "000"}, nil) + engine.On("ContainerCreate", ctx, mock.Anything, mock.Anything, mock.Anything, mock.Anything, "").Return(container.ContainerCreateCreatedBody{ID: "000"}, nil) engine.On("ContainerAttach", ctx, "000", mock.Anything).Return(types.HijackedResponse{ Conn: conn, Reader: bufio.NewReader(strings.NewReader("stress completed")), @@ -940,7 +936,7 @@ func Test_dockerClient_stressContainerCommand(t *testing.T) { ctx: context.TODO(), }, mockInit: func(ctx context.Context, engine *mocks.APIClient, conn *mockConn, targetID string, stressors []string, image string, pool bool) { - engine.On("ContainerCreate", ctx, mock.Anything, mock.Anything, mock.Anything, "").Return(container.ContainerCreateCreatedBody{ID: "000"}, nil) + engine.On("ContainerCreate", ctx, mock.Anything, mock.Anything, mock.Anything, mock.Anything, "").Return(container.ContainerCreateCreatedBody{ID: "000"}, nil) engine.On("ContainerAttach", ctx, "000", mock.Anything).Return(types.HijackedResponse{ Conn: conn, Reader: bufio.NewReader(strings.NewReader("stress completed")), @@ -963,7 +959,7 @@ func Test_dockerClient_stressContainerCommand(t *testing.T) { ctx: context.TODO(), }, mockInit: func(ctx context.Context, engine *mocks.APIClient, conn *mockConn, targetID string, stressors []string, image string, pool bool) { - engine.On("ContainerCreate", ctx, mock.Anything, mock.Anything, mock.Anything, "").Return(container.ContainerCreateCreatedBody{ID: "000"}, nil) + engine.On("ContainerCreate", ctx, mock.Anything, mock.Anything, mock.Anything, mock.Anything, "").Return(container.ContainerCreateCreatedBody{ID: "000"}, nil) engine.On("ContainerAttach", ctx, "000", mock.Anything).Return(types.HijackedResponse{ Conn: conn, Reader: bufio.NewReader(strings.NewReader("stress completed")), @@ -982,7 +978,7 @@ func Test_dockerClient_stressContainerCommand(t *testing.T) { ctx: context.TODO(), }, mockInit: func(ctx context.Context, engine *mocks.APIClient, conn *mockConn, targetID string, stressors []string, image string, pool bool) { - engine.On("ContainerCreate", ctx, mock.Anything, mock.Anything, mock.Anything, "").Return(container.ContainerCreateCreatedBody{ID: "000"}, nil) + engine.On("ContainerCreate", ctx, mock.Anything, mock.Anything, mock.Anything, mock.Anything, "").Return(container.ContainerCreateCreatedBody{ID: "000"}, nil) engine.On("ContainerAttach", ctx, "000", mock.Anything).Return(types.HijackedResponse{ Conn: conn, Reader: bufio.NewReader(strings.NewReader("stress completed")), @@ -1006,7 +1002,7 @@ func Test_dockerClient_stressContainerCommand(t *testing.T) { ctx: context.TODO(), }, mockInit: func(ctx context.Context, engine *mocks.APIClient, conn *mockConn, targetID string, stressors []string, image string, pool bool) { - engine.On("ContainerCreate", ctx, mock.Anything, mock.Anything, mock.Anything, "").Return(container.ContainerCreateCreatedBody{ID: "000"}, nil) + engine.On("ContainerCreate", ctx, mock.Anything, mock.Anything, mock.Anything, mock.Anything, "").Return(container.ContainerCreateCreatedBody{ID: "000"}, nil) engine.On("ContainerAttach", ctx, "000", mock.Anything).Return(types.HijackedResponse{}, errors.New("failed to attach")) }, wantErr: true, @@ -1017,7 +1013,7 @@ func Test_dockerClient_stressContainerCommand(t *testing.T) { ctx: context.TODO(), }, mockInit: func(ctx context.Context, engine *mocks.APIClient, conn *mockConn, targetID string, stressors []string, image string, pool bool) { - engine.On("ContainerCreate", ctx, mock.Anything, mock.Anything, mock.Anything, "").Return(container.ContainerCreateCreatedBody{}, errors.New("failed to create")) + engine.On("ContainerCreate", ctx, mock.Anything, mock.Anything, mock.Anything, mock.Anything, "").Return(container.ContainerCreateCreatedBody{}, errors.New("failed to create")) }, wantErr: true, }, diff --git a/pkg/container/container.go b/pkg/container/container.go index 310797f1..8d83e195 100644 --- a/pkg/container/container.go +++ b/pkg/container/container.go @@ -16,39 +16,30 @@ const ( // Container represents a running Docker container. type Container struct { - containerInfo types.ContainerJSON - imageInfo types.ImageInspect -} - -// NewContainer returns a new Container instance instantiated with the -// specified ContainerJSON and ImageInpsect structures. -func NewContainer(containerInfo types.ContainerJSON, imageInfo types.ImageInspect) *Container { - return &Container{ - containerInfo: containerInfo, - imageInfo: imageInfo, - } + ContainerInfo types.ContainerJSON + ImageInfo types.ImageInspect } // ID returns the Docker container ID. func (c *Container) ID() string { - return c.containerInfo.ID + return c.ContainerInfo.ID } // Name returns the Docker container name. func (c *Container) Name() string { - return c.containerInfo.Name + return c.ContainerInfo.Name } // ImageID returns the ID of the Docker image that was used to start the container. func (c *Container) ImageID() string { - return c.imageInfo.ID + return c.ImageInfo.ID } // ImageName returns the name of the Docker image that was used to start the // container. If the original image was specified without a particular tag, the // "latest" tag is assumed. func (c *Container) ImageName() string { - imageName := c.containerInfo.Image + imageName := c.ContainerInfo.Image if !strings.Contains(imageName, ":") { imageName = fmt.Sprintf("%s:latest", imageName) } @@ -61,8 +52,8 @@ func (c *Container) ImageName() string { func (c *Container) Links() []string { var links []string - if c.containerInfo.NetworkSettings != nil { - networkSettings := c.containerInfo.NetworkSettings + if c.ContainerInfo.NetworkSettings != nil { + networkSettings := c.ContainerInfo.NetworkSettings for _, network := range networkSettings.Networks { for _, link := range network.Links { name := strings.Split(link, ":")[0] @@ -79,7 +70,7 @@ func (c *Container) Links() []string { // identified by the presence of the "com.gaiaadm.pumba" label in // the container metadata. func (c *Container) IsPumba() bool { - val, ok := c.containerInfo.Config.Labels[pumbaLabel] + val, ok := c.ContainerInfo.Config.Labels[pumbaLabel] return ok && val == trueValue } @@ -88,7 +79,7 @@ func (c *Container) IsPumba() bool { // identified by the presence of the "com.gaiaadm.pumba.skip" label in // the container metadata. Use it to skip monitoring and helper containers. func (c *Container) IsPumbaSkip() bool { - val, ok := c.containerInfo.Config.Labels[pumbaSkipLabel] + val, ok := c.ContainerInfo.Config.Labels[pumbaSkipLabel] return ok && val == trueValue } @@ -96,7 +87,7 @@ func (c *Container) IsPumbaSkip() bool { // container's metadata. If the container has not specified a custom stop // signal, the empty string "" is returned. func (c *Container) StopSignal() string { - if val, ok := c.containerInfo.Config.Labels[signalLabel]; ok { + if val, ok := c.ContainerInfo.Config.Labels[signalLabel]; ok { return val } diff --git a/pkg/container/container_test.go b/pkg/container/container_test.go index f537d472..d77664cc 100644 --- a/pkg/container/container_test.go +++ b/pkg/container/container_test.go @@ -8,7 +8,7 @@ import ( func TestID(t *testing.T) { c := Container{ - containerInfo: DetailsResponse(AsMap("ID", "foo")), + ContainerInfo: DetailsResponse(AsMap("ID", "foo")), } assert.Equal(t, "foo", c.ID()) @@ -16,7 +16,7 @@ func TestID(t *testing.T) { func TestName(t *testing.T) { c := Container{ - containerInfo: DetailsResponse(AsMap("Name", "foo")), + ContainerInfo: DetailsResponse(AsMap("Name", "foo")), } assert.Equal(t, "foo", c.Name()) @@ -24,7 +24,7 @@ func TestName(t *testing.T) { func TestImageID(t *testing.T) { c := Container{ - imageInfo: ImageDetailsResponse(AsMap("ID", "foo")), + ImageInfo: ImageDetailsResponse(AsMap("ID", "foo")), } assert.Equal(t, "foo", c.ImageID()) @@ -32,7 +32,7 @@ func TestImageID(t *testing.T) { func TestImageName_Tagged(t *testing.T) { c := Container{ - containerInfo: DetailsResponse(AsMap("Image", "foo:latest")), + ContainerInfo: DetailsResponse(AsMap("Image", "foo:latest")), } assert.Equal(t, "foo:latest", c.ImageName()) @@ -40,7 +40,7 @@ func TestImageName_Tagged(t *testing.T) { func TestImageName_Untagged(t *testing.T) { c := Container{ - containerInfo: DetailsResponse(AsMap("Image", "foo")), + ContainerInfo: DetailsResponse(AsMap("Image", "foo")), } assert.Equal(t, "foo:latest", c.ImageName()) @@ -48,7 +48,7 @@ func TestImageName_Untagged(t *testing.T) { func TestLinks(t *testing.T) { c := Container{ - containerInfo: DetailsResponse(AsMap("Links", []string{"foo:foo", "bar:bar"})), + ContainerInfo: DetailsResponse(AsMap("Links", []string{"foo:foo", "bar:bar"})), } links := c.Links() @@ -62,7 +62,7 @@ func TestIsPumba_True(t *testing.T) { } c := Container{ - containerInfo: DetailsResponse(AsMap("Labels", labels)), + ContainerInfo: DetailsResponse(AsMap("Labels", labels)), } assert.True(t, c.IsPumba()) @@ -74,7 +74,7 @@ func TestIsPumbaSkip_True(t *testing.T) { } c := Container{ - containerInfo: DetailsResponse(AsMap("Labels", labels)), + ContainerInfo: DetailsResponse(AsMap("Labels", labels)), } assert.True(t, c.IsPumbaSkip()) @@ -86,7 +86,7 @@ func TestIsPumba_WrongLabelValue(t *testing.T) { } c := Container{ - containerInfo: DetailsResponse(AsMap("Labels", labels)), + ContainerInfo: DetailsResponse(AsMap("Labels", labels)), } assert.False(t, c.IsPumba()) @@ -96,7 +96,7 @@ func TestIsPumba_NoLabel(t *testing.T) { emptyLabels := map[string]string{} c := Container{ - containerInfo: DetailsResponse(AsMap("Labels", emptyLabels)), + ContainerInfo: DetailsResponse(AsMap("Labels", emptyLabels)), } assert.False(t, c.IsPumba()) @@ -108,7 +108,7 @@ func TestStopSignal_Present(t *testing.T) { } c := Container{ - containerInfo: DetailsResponse(AsMap("Labels", labels)), + ContainerInfo: DetailsResponse(AsMap("Labels", labels)), } assert.Equal(t, "SIGQUIT", c.StopSignal()) @@ -118,7 +118,7 @@ func TestStopSignal_NoLabel(t *testing.T) { emptyLabels := map[string]string{} c := Container{ - containerInfo: DetailsResponse(AsMap("Labels", emptyLabels)), + ContainerInfo: DetailsResponse(AsMap("Labels", emptyLabels)), } assert.Equal(t, "", c.StopSignal()) diff --git a/pkg/container/http_client.go b/pkg/container/http_client.go index 6ae9160d..b04c4ab9 100644 --- a/pkg/container/http_client.go +++ b/pkg/container/http_client.go @@ -7,6 +7,8 @@ import ( "net/http" "net/url" "time" + + "github.com/pkg/errors" ) const defaultTimeout = 30 * time.Second @@ -15,7 +17,7 @@ const defaultTimeout = 30 * time.Second func HTTPClient(daemonURL string, tlsConfig *tls.Config) (*http.Client, error) { u, err := url.Parse(daemonURL) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to parse docker daemon url") } if u.Scheme == "" || u.Scheme == "tcp" { if tlsConfig == nil { @@ -37,12 +39,12 @@ func newHTTPClient(address *url.URL, tlsConfig *tls.Config, timeout time.Duratio switch address.Scheme { default: httpTransport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { - return net.DialTimeout(network, addr, timeout) + return net.DialTimeout(network, addr, timeout) //nolint:wrapcheck } case "unix": socketPath := address.Path unixDial := func(ctx context.Context, network, addr string) (net.Conn, error) { - return net.DialTimeout("unix", socketPath, timeout) + return net.DialTimeout("unix", socketPath, timeout) //nolint:wrapcheck } httpTransport.DialContext = unixDial // Override the main URL object so the HTTP lib won't complain diff --git a/pkg/container/mock_Client.go b/pkg/container/mock_Client.go index ada9228d..e8ea622d 100644 --- a/pkg/container/mock_Client.go +++ b/pkg/container/mock_Client.go @@ -109,13 +109,13 @@ func (_m *MockClient) RemoveContainer(_a0 context.Context, _a1 *Container, _a2 b return r0 } -// RestartContainer provides a mock function with given fields: _a0, _a1, _a2, _a3, _a4 -func (_m *MockClient) RestartContainer(_a0 context.Context, _a1 *Container, _a2 time.Duration, _a3 time.Duration, _a4 bool) error { - ret := _m.Called(_a0, _a1, _a2, _a3, _a4) +// RestartContainer provides a mock function with given fields: _a0, _a1, _a2, _a3 +func (_m *MockClient) RestartContainer(_a0 context.Context, _a1 *Container, _a2 time.Duration, _a3 bool) error { + ret := _m.Called(_a0, _a1, _a2, _a3) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *Container, time.Duration, time.Duration, bool) error); ok { - r0 = rf(_a0, _a1, _a2, _a3, _a4) + if rf, ok := ret.Get(0).(func(context.Context, *Container, time.Duration, bool) error); ok { + r0 = rf(_a0, _a1, _a2, _a3) } else { r0 = ret.Error(0) } diff --git a/pkg/container/sort.go b/pkg/container/sort.go deleted file mode 100644 index 6c898ad3..00000000 --- a/pkg/container/sort.go +++ /dev/null @@ -1,107 +0,0 @@ -package container - -import ( - "time" - - "github.com/pkg/errors" -) - -// ByCreated allows a list of Container structures to be sorted by the container's -// created date. -type ByCreated []Container - -func (c ByCreated) Len() int { return len(c) } -func (c ByCreated) Swap(i, j int) { c[i], c[j] = c[j], c[i] } - -// Less will compare two elements (identified by index) in the Container -// list by created-date. -func (c ByCreated) Less(i, j int) bool { - t1, err := time.Parse(time.RFC3339Nano, c[i].containerInfo.Created) - if err != nil { - t1 = time.Now() - } - - t2, _ := time.Parse(time.RFC3339Nano, c[j].containerInfo.Created) - if err != nil { - t1 = time.Now() - } - - return t1.Before(t2) -} - -// SortByDependencies will sort the list of containers taking into account any -// links between containers. Container with no outgoing links will be sorted to -// the front of the list while containers with links will be sorted after all -// of their dependencies. This sort order ensures that linked containers can -// be started in the correct order. -func SortByDependencies(containers []Container) ([]Container, error) { - sorter := dependencySorter{} - return sorter.Sort(containers) -} - -type dependencySorter struct { - unvisited []Container - marked map[string]bool - sorted []Container -} - -func (ds *dependencySorter) Sort(containers []Container) ([]Container, error) { - ds.unvisited = containers - ds.marked = map[string]bool{} - - for len(ds.unvisited) > 0 { - if err := ds.visit(ds.unvisited[0]); err != nil { - return nil, err - } - } - - return ds.sorted, nil -} - -func (ds *dependencySorter) visit(c Container) error { - - if _, ok := ds.marked[c.Name()]; ok { - return errors.Errorf("Circular reference to %s", c.Name()) - } - - // Mark any visited node so that circular references can be detected - ds.marked[c.Name()] = true - defer delete(ds.marked, c.Name()) - - // Recursively visit links - for _, linkName := range c.Links() { - if linkedContainer := ds.findUnvisited(linkName); linkedContainer != nil { - if err := ds.visit(*linkedContainer); err != nil { - return err - } - } - } - - // Move container from unvisited to sorted - ds.removeUnvisited(c) - ds.sorted = append(ds.sorted, c) - - return nil -} - -func (ds *dependencySorter) findUnvisited(name string) *Container { - for _, c := range ds.unvisited { - if c.Name() == name { - return &c - } - } - - return nil -} - -func (ds *dependencySorter) removeUnvisited(c Container) { - var idx int - for i := range ds.unvisited { - if ds.unvisited[i].Name() == c.Name() { - idx = i - break - } - } - - ds.unvisited = append(ds.unvisited[0:idx], ds.unvisited[idx+1:]...) -} diff --git a/pkg/container/sort_test.go b/pkg/container/sort_test.go deleted file mode 100644 index 6c582387..00000000 --- a/pkg/container/sort_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package container - -import ( - "sort" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestByCreated(t *testing.T) { - c1 := containerCreatedAt("2015-07-01T12:00:01.000000000Z") - c2 := containerCreatedAt("2015-07-01T12:00:02.000000000Z") - c3 := containerCreatedAt("2015-07-01T12:00:02.000000001Z") - cs := []Container{c3, c2, c1} - - sort.Sort(ByCreated(cs)) - - assert.Equal(t, []Container{c1, c2, c3}, cs) -} - -func TestSortByDependencies_Success(t *testing.T) { - c1 := containerWithLinks("1", []string{}) - c2 := containerWithLinks("2", []string{"1:"}) - c3 := containerWithLinks("3", []string{"2:"}) - c4 := containerWithLinks("4", []string{"3:"}) - c5 := containerWithLinks("5", []string{"4:"}) - c6 := containerWithLinks("6", []string{"5:", "3:"}) - containers := []Container{c6, c2, c4, c1, c3, c5} - - result, err := SortByDependencies(containers) - - assert.NoError(t, err) - assert.Equal(t, []Container{c1, c2, c3, c4, c5, c6}, result) -} - -func TestSortByDependencies_Error(t *testing.T) { - c1 := containerWithLinks("1", []string{"3:"}) - c2 := containerWithLinks("2", []string{"1:"}) - c3 := containerWithLinks("3", []string{"2:"}) - containers := []Container{c1, c2, c3} - - _, err := SortByDependencies(containers) - - assert.Error(t, err) - assert.EqualError(t, err, "Circular reference to 1") -} - -func containerCreatedAt(creationDate string) Container { - return Container{ - containerInfo: DetailsResponse(AsMap("Created", creationDate)), - } -} - -func containerWithLinks(name string, links []string) Container { - return Container{ - containerInfo: DetailsResponse(AsMap( - "Name", name, - "Links", links, - )), - } -} diff --git a/pkg/container/test_helper.go b/pkg/container/test_helper.go index 9807a6ff..54790a8f 100644 --- a/pkg/container/test_helper.go +++ b/pkg/container/test_helper.go @@ -8,10 +8,10 @@ import ( func CreateTestContainers(count int) []*Container { containers := []*Container{} for i := 0; i < count; i++ { - containers = append(containers, NewContainer( + containers = append(containers, &Container{ DetailsResponse(AsMap("Name", fmt.Sprintf("c%d", i))), ImageDetailsResponse(AsMap()), - )) + }) } return containers } @@ -20,10 +20,10 @@ func CreateTestContainers(count int) []*Container { func CreateLabeledTestContainers(count int, labels map[string]string) []*Container { containers := []*Container{} for i := 0; i < count; i++ { - containers = append(containers, NewContainer( + containers = append(containers, &Container{ DetailsResponse(AsMap("Name", fmt.Sprintf("c%d", i), "Labels", labels)), ImageDetailsResponse(AsMap()), - )) + }) } return containers } diff --git a/pkg/container/util.go b/pkg/container/util.go index 393aec70..0068a366 100644 --- a/pkg/container/util.go +++ b/pkg/container/util.go @@ -5,6 +5,8 @@ import ( "math/rand" "regexp" "time" + + "github.com/pkg/errors" ) // ListOpts list options @@ -13,8 +15,8 @@ type ListOpts struct { Labels []string } -// Filter list filter -type Filter struct { +// list filter +type filter struct { Names []string Pattern string Opts ListOpts @@ -45,26 +47,26 @@ func matchPattern(pattern, containerName string) bool { return matched } -func applyContainerFilter(filter Filter) FilterFunc { +func applyContainerFilter(flt filter) FilterFunc { return func(c *Container) bool { // skip Pumba label if c.IsPumba() || c.IsPumbaSkip() { return false } // if not requested all - if !filter.Opts.All { + if !flt.Opts.All { // match names - if len(filter.Names) > 0 { - return matchNames(filter.Names, c.containerInfo.Name) + if len(flt.Names) > 0 { + return matchNames(flt.Names, c.ContainerInfo.Name) } - return matchPattern(filter.Pattern, c.containerInfo.Name) + return matchPattern(flt.Pattern, c.ContainerInfo.Name) } return true } } func listContainers(ctx context.Context, client Client, names []string, pattern string, labels []string, all bool) ([]*Container, error) { - filter := Filter{ + f := filter{ Names: names, Pattern: pattern, Opts: ListOpts{ @@ -72,13 +74,17 @@ func listContainers(ctx context.Context, client Client, names []string, pattern Labels: labels, }, } - return client.ListContainers(ctx, applyContainerFilter(filter), filter.Opts) + containers, err := client.ListContainers(ctx, applyContainerFilter(f), f.Opts) + if err != nil { + return nil, errors.Wrap(err, "failed to list containers") + } + return containers, nil } // RandomContainer select random container func RandomContainer(containers []*Container) *Container { if len(containers) > 0 { - r := rand.New(rand.NewSource(time.Now().UnixNano())) + r := rand.New(rand.NewSource(time.Now().UnixNano())) //nolint:gosec i := r.Intn(len(containers)) return containers[i] } @@ -91,13 +97,12 @@ func ListNContainers(ctx context.Context, client Client, names []string, pattern if err != nil { return nil, err } - - if len(containers) > limit && limit > 0 { - for i := range containers { - j := rand.Intn(i + 1) + if limit > 0 && len(containers) > limit { + rand.Seed(time.Now().UnixNano()) + rand.Shuffle(len(containers), func(i, j int) { containers[i], containers[j] = containers[j], containers[i] - } - return containers[0:limit], nil + }) + containers = containers[0:limit] } return containers, nil diff --git a/pkg/util/util.go b/pkg/util/util.go index a10349e9..5bc86d38 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -1,11 +1,9 @@ package util import ( - "fmt" "net" "strconv" "strings" - "time" "github.com/pkg/errors" ) @@ -20,19 +18,6 @@ func SliceContains(slice []string, item string) bool { return ok } -// GetIntervalValue get interval value from string duration -func GetIntervalValue(interval string) (time.Duration, error) { - // get recurrent time interval - if interval == "" { - return 0, nil - } - i, err := time.ParseDuration(interval) - if err != nil { - return 0, errors.Wrap(err, "failed to parse interval") - } - return i, nil -} - // GetPorts will split the string of comma separated ports and return a list of ports func GetPorts(ports string) ([]string, error) { portList := strings.Split(ports, ",") @@ -57,38 +42,19 @@ func verifyPort(port string) error { if port == "" { return nil } - portNum, err := strconv.ParseInt(port, 10, 64) + portNum, err := strconv.ParseInt(port, 10, 64) //nolint:gomnd if err != nil { return errors.Wrap(err, "failed to parse port as number") } if portNum < 0 || portNum > 65535 { - return fmt.Errorf("Port is either below 0 or greater than 65535: " + port) + return errors.Errorf("Port is either below 0 or greater than 65535: %s", port) } return nil } -// GetDurationValue get duration and make sure it's smaller than interval -func GetDurationValue(durationStr string, interval time.Duration) (time.Duration, error) { - var err error - var duration time.Duration - if durationStr == "" { - return 0, errors.New("undefined duration") - } - if durationStr != "" { - duration, err = time.ParseDuration(durationStr) - if err != nil { - return 0, errors.Wrap(err, "failed to parse duration") - } - } - if interval != 0 && duration >= interval { - return 0, errors.New("duration must be shorter than interval") - } - return duration, nil -} - -// CIDRNotation Ensure IP string is in CIDR notation -func CIDRNotation(ip string) string { +// ensure IP string is in CIDR notation +func cidrNotation(ip string) string { if !strings.Contains(ip, "/") { return ip + "/32" } @@ -97,10 +63,10 @@ func CIDRNotation(ip string) string { // ParseCIDR Parse IP string to IPNet func ParseCIDR(ip string) (*net.IPNet, error) { - cidr := CIDRNotation(ip) + cidr := cidrNotation(ip) _, ipNet, err := net.ParseCIDR(cidr) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to parse CIDR") } return ipNet, nil } diff --git a/tests/global.bats b/tests/global.bats index 2f1d9cea..8b0f9002 100644 --- a/tests/global.bats +++ b/tests/global.bats @@ -9,10 +9,3 @@ run pumba help [ $status -eq 0 ] } - -@test "Pumba Version" { - ver=$(cat VERSION) - run pumba --version - [ $status -eq 0 ] - [[ $output == *"$ver"* ]] -} diff --git a/tests/kill_container.bats b/tests/kill.bats similarity index 90% rename from tests/kill_container.bats rename to tests/kill.bats index 57d60ac9..2e878df6 100644 --- a/tests/kill_container.bats +++ b/tests/kill.bats @@ -12,10 +12,7 @@ # and (container has been killed) run docker inspect -f {{.State.Status}} killing_victim - [[ $output == "exited" ]] - - # cleanup - docker rm -f killing_victim || true + [ $output = "exited" ] } @test "Should kill running labeled container with default signal" { @@ -32,14 +29,16 @@ # and (container has been killed) run docker inspect -f {{.State.Status}} killing_victim_1 - [[ $output == "exited" ]] + [ $output = "exited" ] # and (container has been killed) run docker inspect -f {{.State.Status}} killing_victim_2 - [[ $output == "exited" ]] + [ $output = "exited" ] # and (container has not been killed) run docker inspect -f {{.State.Status}} killing_victim_3 - [[ $output == "running" ]] + [ $output = "running" ] +} - # cleanup +teardown() { + docker rm -f killing_victim || true docker rm -f killing_victim_1 killing_victim_2 killing_victim_3 || true } \ No newline at end of file diff --git a/tests/netem.bats b/tests/netem.bats index 0e28da05..a5ccfce3 100644 --- a/tests/netem.bats +++ b/tests/netem.bats @@ -1,28 +1,28 @@ #!/usr/bin/env bats -@test "Netem Help" { +@test "Should display netem help" { run pumba netem --help [ $status -eq 0 ] } -@test "Netem Delay Help" { +@test "Should display netem delay help" { run pumba netem delay --help [ $status -eq 0 ] } -@test "Netem Delay Undefined Duration" { +@test "Should fail when Duration is unset for netem delay" { run pumba netem delay --time 100 [ $status -eq 1 ] - [[ ${lines[0]} =~ "undefined duration" ]] + [[ ${lines[0]} =~ "unset or invalid duration value" ]] } -@test "Netem Delay 200ms" { +@test "Should delay egress traffic from container" { run pumba netem --duration 200ms delay --time 100 test [ $status -eq 0 ] [[ $output =~ "no containers found" ]] } -@test "Netem Delay 200ms External Image" { +@test "Should delay egress traffic from container with external tc image" { # start ping container in background docker run -dit --name pingtest alpine ping 1.1.1.1 cid=$(docker ps -q --filter "name=pingtest") @@ -36,6 +36,8 @@ [ $status -eq 0 ] [[ $output =~ "running netem on container" ]] [[ $output =~ "stopping netem on container" ]] - # cleanup - docker rm -f pingtest || true +} + +teardown() { + docker rm -f pingtest || true } diff --git a/tests/pause_container.bats b/tests/pause.bats similarity index 84% rename from tests/pause_container.bats rename to tests/pause.bats index b56f3dfd..e6517439 100644 --- a/tests/pause_container.bats +++ b/tests/pause.bats @@ -1,3 +1,5 @@ +#!/usr/bin/env bats + @test "Should pause running container" { # given (started container) docker run -d --name pausing_victim alpine tail -f /dev/null @@ -8,13 +10,14 @@ # then (container has been paused) run docker inspect -f {{.State.Status}} pausing_victim - [[ $output == "paused" ]] + [ $output == "paused" ] # and (container has been resumed) sleep 4 run docker inspect -f {{.State.Status}} pausing_victim - [[ $output == "running" ]] + [ $output = "running" ] +} - # cleanup +teardown() { docker rm -f pausing_victim || true } \ No newline at end of file diff --git a/tests/remove_container.bats b/tests/remove.bats similarity index 100% rename from tests/remove_container.bats rename to tests/remove.bats diff --git a/tests/restart.bats b/tests/restart.bats new file mode 100644 index 00000000..ef9deacf --- /dev/null +++ b/tests/restart.bats @@ -0,0 +1,26 @@ +#!/usr/bin/env bats + +@test "Should restart running container" { + # given (started container) + docker run -d --name restart_victim alpine tail -f /dev/null + + # capture container started time + started_time="$(docker inspect -f '{{.State.StartedAt}}' restart_victim)" + + # when (trying to restart container) + run pumba restart --timeout 3s restart_victim & + sleep 5 + + # then (container has been restarted) + restarted_time="$(docker inspect -f '{{.State.StartedAt}}' restart_victim)" + + [ "$started_time" != "$restarted_time" ] + + # and (container has been restarted) + run docker inspect -f {{.State.Status}} restart_victim + [ $output = "running" ] +} + +teardown() { + docker rm -f restart_victim || true +} \ No newline at end of file diff --git a/tests/skip_ci/README.md b/tests/skip_ci/README.md new file mode 100644 index 00000000..347c1270 --- /dev/null +++ b/tests/skip_ci/README.md @@ -0,0 +1,9 @@ +## Note + +Integration tests in this directory will not be run in the GitHub CI workflow. + +Run all tests manually with: + +```shell +bats --print-output-on-failure --recursive /tests +``` \ No newline at end of file diff --git a/tests/skip_ci/stress.bats b/tests/skip_ci/stress.bats new file mode 100644 index 00000000..2d75b7de --- /dev/null +++ b/tests/skip_ci/stress.bats @@ -0,0 +1,22 @@ +#!/usr/bin/env bats + +@test "Should stress running container" { + # given (started container) + docker run -d --name stress_victim alpine tail -f /dev/null + + # pull stress-ng image + run docker pull alexeiled/stress-ng:latest-ubuntu + + # when (trying to stress container) + run pumba --log-level=debug stress --duration=20s --stressors="--cpu 1 --timeout 10s" stress_victim + [ $status -eq 0 ] + [[ $output =~ "stress testing container" ]] + + # check number of stress-ng processes + stress_count=$(docker top stress_victim -o pid,command | grep stress-ng | wc -l) + [ "$stress_count" -eq 0 ] +} + +teardown() { + docker rm -f stress_victim || true +} \ No newline at end of file diff --git a/tests/stop_container.bats b/tests/stop.bats similarity index 92% rename from tests/stop_container.bats rename to tests/stop.bats index ef97b0ae..a213faa5 100644 --- a/tests/stop_container.bats +++ b/tests/stop.bats @@ -12,7 +12,7 @@ # and (container has been stopped) run docker inspect -f {{.State.Status}} stopping_victim - [[ $output == "exited" ]] + [ $output = "exited" ] # cleanup docker rm -f stopping_victim || true @@ -30,7 +30,7 @@ # and (container has been stopped) run docker inspect -f {{.State.Status}} stopping_victim - [[ $output == "exited" ]] + [ $output = "exited" ] # cleanup docker rm -f stopping_victim || true @@ -40,7 +40,7 @@ # given (stopped container) run docker run -d --name starting_victim alpine sh -c "trap : TERM INT; tail -f /dev/null & wait" run docker inspect -f {{.State.Status}} starting_victim - [[ $output == "running" ]] + [ $output = "running" ] # when (trying to stop container) run pumba stop --restart=true --duration=5s /starting_victim @@ -49,7 +49,7 @@ # and (container has been (re)started) run docker inspect -f {{.State.Status}} starting_victim - [[ $output == "running" ]] + [ $output = "running" ] # cleanup docker rm -f starting_victim || true