diff --git a/.dockerignore b/.dockerignore index a21b7936a..67f315881 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,3 @@ .git/ bin/ dist/ -tests/node-client/node_modules/ diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index f0c1edcf7..a9540a5a5 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -49,29 +49,16 @@ Briefly describe the problem you are having in a few paragraphs. **Additional information you deem important (e.g. issue happens only occasionally):** -**Output of `docker-compose --version`:** +**Output of `docker compose version`:** ``` (paste your output here) ``` -**Output of `docker version`:** - -``` -(paste your output here) -``` - -**Output of `docker context show`:** -You can also run `docker context inspect context-name` to give us more details but don't forget to remove sensitive content. - -``` -(paste your output here) -``` - **Output of `docker info`:** ``` (paste your output here) ``` -**Additional environment details (AWS ECS, Azure ACI, local, etc.):** +**Additional environment details:** diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index a80dc1651..5fa9759fe 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,12 +3,4 @@ **Related issue** - - **(not mandatory) A picture of a cute animal, if possible in relation with what you did** diff --git a/.github/labeler.yml b/.github/labeler.yml deleted file mode 100644 index 46e182335..000000000 --- a/.github/labeler.yml +++ /dev/null @@ -1,27 +0,0 @@ -aci: - - aci/**/* - -ecs: - - ecs/**/* - -local: - - local/**/* - -kube: - - kube/**/* - -cli: - - cli/**/* - -metrics: - - cli/metrics/**/* - -api: - - api/**/* - - cli/server/protos/**/* - -ci: - - .github/**/* - -documentation: - - docs/**/* diff --git a/.github/workflows/aci-tests.yml b/.github/workflows/aci-tests.yml deleted file mode 100644 index 2854f62b1..000000000 --- a/.github/workflows/aci-tests.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: ACI integration tests - -on: - push: - branches: - - main - pull_request: - -jobs: - check-optional-tests: - name: Check if needs to run ACI tests - runs-on: ubuntu-latest - outputs: - trigger-aci: ${{steps.runacitest.outputs.triggered}} - steps: - - uses: khan/pull-request-comment-trigger@master - name: Check if test ACI - if: github.event_name == 'pull_request' - id: runacitest - with: - trigger: '/test-aci' - - aci-tests: - name: ACI e2e tests - runs-on: ubuntu-latest - env: - GO111MODULE: "on" - needs: check-optional-tests - if: github.ref == 'refs/heads/main' || needs.check-optional-tests.outputs.trigger-aci == 'true' - steps: - - name: Set up Go 1.16 - uses: actions/setup-go@v2 - with: - go-version: 1.16 - id: go - - - name: Setup docker CLI - run: | - curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.3.tgz | tar xz - sudo cp ./docker/docker /usr/bin/ && rm -rf docker && docker version - - - name: Checkout code into the Go module directory - uses: actions/checkout@v2 - - - uses: actions/cache@v2 - with: - path: ~/go/pkg/mod - key: go-${{ hashFiles('**/go.sum') }} - - - name: Build for ACI e2e tests - run: make -f builder.Makefile cli - - - name: ACI e2e Test - env: - AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} - AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} - AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} - run: make e2e-aci diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65710f966..ada2146aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: Continuous integration on: push: branches: - - main + - v2 pull_request: jobs: @@ -25,12 +25,9 @@ jobs: - name: Validate go-mod is up-to-date and license headers run: make validate - - name: Validate imports - run: make import-restrictions - - name: Run golangci-lint env: - BUILD_TAGS: kube,e2e + BUILD_TAGS: e2e run: | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sudo sh -s -- -b /usr/bin/ v1.39.0 make -f builder.Makefile lint @@ -60,7 +57,7 @@ jobs: # Ensure we don't discover cross platform build issues at release time. # Time used to build linux here is gained back in the build for local E2E step - name: Build packages - run: make -f builder.Makefile cross cross-compose-plugin + run: make -f builder.Makefile cross build: name: Build @@ -99,7 +96,7 @@ jobs: - name: Build for local E2E env: BUILD_TAGS: e2e - run: make -f builder.Makefile cli compose-plugin + run: make -f builder.Makefile compose-plugin - name: E2E Test run: make e2e-compose diff --git a/.github/workflows/cli-release.yaml b/.github/workflows/cli-release.yaml deleted file mode 100644 index 4df63e519..000000000 --- a/.github/workflows/cli-release.yaml +++ /dev/null @@ -1,50 +0,0 @@ -name: Releaser - -on: - workflow_dispatch: - inputs: - tag: - description: 'Release Tag' - required: true - dry-run: - description: 'Dry run' - required: false - default: 'true' -jobs: - release: - runs-on: ubuntu-latest - steps: - - name: Set up Go 1.16 - uses: actions/setup-go@v2 - with: - go-version: 1.16 - id: go - - - name: Setup docker CLI - run: | - curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.3.tgz | tar xz - sudo cp ./docker/docker /usr/bin/ && rm -rf docker && docker version - - - name: Checkout code into the Go module directory - uses: actions/checkout@v2 - - - uses: actions/cache@v2 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: Build - run: make -f builder.Makefile cross - - - name: License - run: cp packaging/* bin/ - - - uses: ncipollo/release-action@v1 - if: ${{ github.event.inputs.dry-run != 'true' }} - with: - artifacts: "bin/*" - prerelease: true - token: ${{ secrets.GITHUB_TOKEN }} - tag: ${{ github.event.inputs.tag }} diff --git a/.github/workflows/ecs-tests.yml b/.github/workflows/ecs-tests.yml deleted file mode 100644 index c19f67cd2..000000000 --- a/.github/workflows/ecs-tests.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: ECS integration tests - -on: - push: - branches: - - main - pull_request: - -jobs: - check-optional-tests: - name: Check if needs to run ECS tests - runs-on: ubuntu-latest - outputs: - trigger-ecs: ${{steps.runecstest.outputs.triggered}} - steps: - - uses: khan/pull-request-comment-trigger@master - name: Check if test ECS - if: github.event_name == 'pull_request' - id: runecstest - with: - trigger: '/test-ecs' - - - ecs-tests: - name: ECS e2e tests - runs-on: ubuntu-latest - env: - GO111MODULE: "on" - needs: check-optional-tests - if: github.ref == 'refs/heads/main' || needs.check-optional-tests.outputs.trigger-ecs == 'true' - steps: - - name: Set up Go 1.16 - uses: actions/setup-go@v2 - with: - go-version: 1.16 - id: go - - - name: Setup docker CLI - run: | - curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.3.tgz | tar xz - sudo cp ./docker/docker /usr/bin/ && rm -rf docker && docker version - - - name: Checkout code into the Go module directory - uses: actions/checkout@v2 - - - uses: actions/cache@v2 - with: - path: ~/go/pkg/mod - key: go-${{ hashFiles('**/go.sum') }} - - - name: Build for ECS e2e tests - run: make -f builder.Makefile cli - - - name: create aws config folder - run: mkdir -p ~/.aws - - - name: ECS e2e Test - env: - AWS_DEFAULT_REGION: us-west-2 - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }} - run: make e2e-ecs diff --git a/.github/workflows/kube-tests.yml b/.github/workflows/kube-tests.yml deleted file mode 100644 index 932ea6abf..000000000 --- a/.github/workflows/kube-tests.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Kube integration tests - -on: - push: - branches: - - main - pull_request: - -jobs: - check-optional-tests: - name: Check if needs to run Kube tests - runs-on: ubuntu-latest - outputs: - trigger-kube: ${{steps.runkubetest.outputs.triggered}} - steps: - - uses: khan/pull-request-comment-trigger@master - name: Check if test Kube - if: github.event_name == 'pull_request' - id: runkubetest - with: - trigger: '/test-kube' - - - kube-tests: - name: Kube e2e tests - runs-on: ubuntu-latest - env: - GO111MODULE: "on" - needs: check-optional-tests - if: github.ref == 'refs/heads/main' || needs.check-optional-tests.outputs.trigger-kube == 'true' - steps: - - name: Set up Go 1.16 - uses: actions/setup-go@v2 - with: - go-version: 1.16 - id: go - - - name: Setup docker CLI - run: | - curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.3.tgz | tar xz - sudo cp ./docker/docker /usr/bin/ && rm -rf docker && docker version - - - name: Setup Kube tools - run: | - sudo apt-get install jq && jq --version - curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.10.0/kind-linux-amd64 && chmod +x ./kind && sudo mv ./kind /usr/bin/ && kind version - curl -LO "https://dl.k8s.io/release/v1.20.2/bin/linux/amd64/kubectl" && sudo mv kubectl /usr/bin/ && kubectl version --client - - - name: Checkout code into the Go module directory - uses: actions/checkout@v2 - - - uses: actions/cache@v2 - with: - path: ~/go/pkg/mod - key: go-${{ hashFiles('**/go.sum') }} - - - name: Build for Kube e2e tests - env: - BUILD_TAGS: kube - run: make -f builder.Makefile cli - - - name: Kube e2e Test - run: make e2e-kube diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml deleted file mode 100644 index 702783a0b..000000000 --- a/.github/workflows/labeler.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: 'PR labeler' -on: - - pull_request_target - -jobs: - triage: - runs-on: ubuntu-latest - steps: - - uses: actions/labeler@v2.1.1 - with: - repo-token: '${{ secrets.GITHUB_TOKEN }}' \ No newline at end of file diff --git a/.github/workflows/pending_answered.yml b/.github/workflows/pending_answered.yml deleted file mode 100644 index 02c46f901..000000000 --- a/.github/workflows/pending_answered.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Remove pending label on answer - -on: - issue_comment: - types: [ created ] - -jobs: - pending: - if: ${{ !github.event.issue.pull_request && contains(github.event.issue.labels.*.name, 'pending') }} - name: Remove pending label - runs-on: ubuntu-latest - steps: - - name: removelabel - uses: siegerts/pending-response@v1 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - pending-response-label: pending diff --git a/.github/workflows/plugin-release.yaml b/.github/workflows/release.yaml similarity index 100% rename from .github/workflows/plugin-release.yaml rename to .github/workflows/release.yaml diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml deleted file mode 100644 index 4dff90e50..000000000 --- a/.github/workflows/windows-ci.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Windows CI - -on: - push: - branches: - - main - pull_request: - -jobs: - check-optional-tests: - name: Check if needs to run Windows build and tests - runs-on: ubuntu-latest - outputs: - trigger-windows: ${{steps.runwindowstest.outputs.triggered}} - steps: - - uses: khan/pull-request-comment-trigger@master - name: Check if test Windows - if: github.event_name == 'pull_request' - id: runwindowstest - with: - trigger: '/test-windows' - - - windows-build: - name: Windows Build - runs-on: windows-latest - env: - GO111MODULE: "on" - needs: check-optional-tests - if: github.ref == 'refs/heads/main' || needs.check-optional-tests.outputs.trigger-windows == 'true' - steps: - - name: Set up Go 1.16 - uses: actions/setup-go@v2 - with: - go-version: 1.16 - id: go - - - name: Setup docker CLI - run: | - docker version - curl -L -o docker.exe https://github.com/StefanScherer/docker-cli-builder/releases/download/20.10.5/docker.exe - mv -Force ./docker.exe "C:\Program Files\Docker\" - docker version - - - name: Checkout code into the Go module directory - uses: actions/checkout@v2 - - - uses: actions/cache@v2 - with: - path: ~/go/pkg/mod - key: go-${{ hashFiles('**/go.sum') }} - - - name: Test - run: make -f builder.Makefile test - - - name: Build - env: - BUILD_TAGS: e2e - run: make -f builder.Makefile cli - - - name: E2E Test - run: make e2e-win-ci - - - name: ACI e2e Test - env: - AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} - AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} - AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} - # need to docker logout on windows nodes, it seems GHActions does a `docker login --user githubactions` specifically on windows nodes - run: | - docker logout - make e2e-aci diff --git a/BUILDING.md b/BUILDING.md index 57d3cbd95..a1f22be74 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -19,32 +19,11 @@ Once you have the prerequisites installed, you can build the CLI using: make ``` -This will output a CLI for your host machine in `./bin`. - -You will then need to make sure that you have the existing Docker CLI in your -`PATH` with the name `com.docker.cli`. A make target is provided to help with -this: - -```console -make moby-cli-link -``` - -This will create a symbolic link from the existing Docker CLI to -`/usr/local/bin` with the name `com.docker.cli`. +This will output a `docker-compose` CLI plugin for your host machine in `./bin`. You can statically cross compile the CLI for Windows, macOS, and Linux using the `cross` target. -### Updating the API code - -The API provided by the CLI is defined using protobuf. If you make changes to -the `.proto` files in [`protos/`](./protos), you will need to regenerate the API -code: - -```console -make protos -``` - ### Unit tests To run all of the unit tests, run: @@ -57,69 +36,20 @@ If you need to update a golden file simply do `go test ./... -test.update-golden ### End to end tests -#### Local tests - -To run the local end to end tests, run: +To run the end to end tests, run: ```console -make e2e-local +make e2e-compose ``` -Note that this requires the CLI to be built and a local Docker Engine to be -running. - -#### ACI tests - -To run the end to end ACI tests, you will first need to have an Azure account -and have created a service principal. You can create a service principle using -the Azure CLI after you have done a `docker login azure`: - -```console -$ az login # az login is not synced with docker azure login, need az login for next step -$ az ad sp create-for-rbac --name 'MyTestServicePrincipal' --sdk-auth -``` - -You can then run the ACI tests using the `e2e-aci` target with the various -`AZURE_` environment variables set: - -```console -AZURE_TENANT_ID="xxx" AZURE_CLIENT_ID="yyy" AZURE_CLIENT_SECRET="yyy" make e2e-aci -``` - -Running the ACI tests will override your local login and the service principal -credentials use a token that cannot be refreshed automatically. - -*Note:* You will need to rerun `docker login azure` if you would like to use the -CLI after running the ACI tests. - -You can also run a single ACI test by specifying the test name with the -`E2E_TEST` variable: -```console -AZURE_TENANT_ID="xxx" AZURE_CLIENT_ID="yyy" AZURE_CLIENT_SECRET="yyy" make E2E_TEST=TestContainerRun e2e-aci -``` - -#### ECS tests - -To run the end to end ECS tests, you will need to have an AWS account and have -credentials for it in the `~/.aws/credentials` file. - -You can then use the `e2e-ecs` target: - -```console -TEST_AWS_PROFILE=myProfile TEST_AWS_REGION=eu-west-3 make e2e-ecs -``` - -## ACI CI - -ACI CI runs E2E tests and needs the same credentials as described above to run these. 3 secrets are defined in github settings, and accessed by the CI job. -To rotate these secrets, run the same `az ad sp create-for-rbac` command and update 3 github secrets with the resulting new service provide info. +Note that this requires a local Docker Engine to be running. ## Releases To create a new release: * Check that the CI is green on the main branch for commit you want to release -* Create a new tag of the form vx.y.z, following existing tags, and push the tag +* Run the release Github Actions workflow with a tag of the form vx.y.z following existing tags. -Pushing the tag will automatically create a new release and make binaries for +This will automatically create a new tag, release and make binaries for Windows, macOS, and Linux available for download on the -[releases page](https://github.com/docker/compose-cli/releases). +[releases page](https://github.com/docker/compose/releases). diff --git a/Dockerfile b/Dockerfile index c3720bc01..46a8558a9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,14 +32,6 @@ RUN --mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ go mod download -FROM base AS make-protos -ARG PROTOC_GEN_GO_VERSION -RUN go get github.com/golang/protobuf/protoc-gen-go@${PROTOC_GEN_GO_VERSION} -COPY . . -RUN make -f builder.Makefile protos - -FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION} AS lint-base - FROM base AS lint ENV CGO_ENABLED=0 COPY --from=lint-base /usr/bin/golangci-lint /usr/bin/golangci-lint @@ -53,30 +45,6 @@ RUN --mount=target=. \ GIT_TAG=${GIT_TAG} \ make -f builder.Makefile lint -FROM base AS import-restrictions-base -RUN go get github.com/docker/import-restrictions - -FROM import-restrictions-base AS import-restrictions -RUN --mount=target=. \ - --mount=type=cache,target=/go/pkg/mod \ - --mount=type=cache,target=/root/.cache/go-build \ - make -f builder.Makefile import-restrictions - -FROM base AS make-cli -ENV CGO_ENABLED=0 -ARG TARGETOS -ARG TARGETARCH -ARG BUILD_TAGS -ARG GIT_TAG -RUN --mount=target=. \ - --mount=type=cache,target=/go/pkg/mod \ - --mount=type=cache,target=/root/.cache/go-build \ - GOOS=${TARGETOS} \ - GOARCH=${TARGETARCH} \ - BUILD_TAGS=${BUILD_TAGS} \ - GIT_TAG=${GIT_TAG} \ - make BINARY=/out/docker -f builder.Makefile cli - FROM base AS make-compose-plugin ENV CGO_ENABLED=0 ARG TARGETOS @@ -100,13 +68,7 @@ RUN --mount=target=. \ --mount=type=cache,target=/root/.cache/go-build \ BUILD_TAGS=${BUILD_TAGS} \ GIT_TAG=${GIT_TAG} \ - make BINARY=/out/docker COMPOSE_BINARY=/out/docker-compose -f builder.Makefile cross - -FROM scratch AS protos -COPY --from=make-protos /compose-cli/cli/server/protos . - -FROM scratch AS cli -COPY --from=make-cli /out/* . + make COMPOSE_BINARY=/out/docker-compose -f builder.Makefile cross FROM scratch AS compose-plugin COPY --from=make-compose-plugin /out/* . diff --git a/INSTALL.md b/INSTALL.md deleted file mode 100644 index 542b8abb9..000000000 --- a/INSTALL.md +++ /dev/null @@ -1,106 +0,0 @@ -# Mac and Windows installation - -The Compose CLI is built into Docker Desktop Edge and Stable. -You can download it from these links: -- [macOS](https://hub.docker.com/editions/community/docker-ce-desktop-mac) -- [Windows](https://hub.docker.com/editions/community/docker-ce-desktop-windows) - -# Ubuntu Linux installation - -The Linux installation script and manual install instructions have been tested -with a fresh install of Ubuntu 20.04. - -## Prerequisites - -* [Docker 19.03 or later](https://docs.docker.com/engine/install/) - -## Install script - -You can install the Compose CLI using the install script: - -```console -curl -L https://raw.githubusercontent.com/docker/compose-cli/main/scripts/install/install_linux.sh | sh -``` - -## Manual install - -You can download the Compose CLI from [latest release](https://github.com/docker/compose-cli/releases/latest). - -You will then need to extract it and make it executable: - -```console -$ tar xzf docker-linux-amd64.tar.gz -$ chmod +x docker/docker -``` - -To enable using the local Docker Engine and to use existing Docker contexts, you -will need to have the existing Docker CLI as `com.docker.cli` somewhere in your -`PATH`. You can do this by creating a symbolic link from the existing Docker -CLI. - -```console -ln -s /path/to/existing/docker /directory/in/PATH/com.docker.cli -``` - -> **Note**: The `PATH` environment variable is a colon separated list of -> directories with priority from left to right. You can view it using -> `echo $PATH`. You can find the path to the existing Docker CLI using -> `which docker`. You may need root permissions to make this link. - -On a fresh install of Ubuntu 20.04 with Docker Engine -[already installed](https://docs.docker.com/engine/install/ubuntu/): - -```console -$ echo $PATH -/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin -$ which docker -/usr/bin/docker -$ sudo ln -s /usr/bin/docker /usr/local/bin/com.docker.cli -``` - -You can verify that this is working by checking that the new CLI works with the -default context: - -```console -$ ./docker/docker --context default ps -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -$ echo $? -0 -``` - -To make the Compose CLI your default Docker CLI, you must move it to a directory -in your `PATH` with higher priority than the existing Docker CLI. - -Again on a fresh Ubuntu 20.04: - -```console -$ which docker -/usr/bin/docker -$ echo $PATH -/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin -$ sudo mv docker/docker /usr/local/bin/docker -$ which docker -/usr/local/bin/docker -$ docker version -... - Cloud integration 0.1.6 -... -``` - -# Uninstall - -To remove this CLI, you need to remove the binary you downloaded and -`com.docker.cli` from your `PATH`. If you installed using the script, this can -be done as follows: - -```console -sudo rm /usr/local/bin/docker /usr/local/bin/com.docker.cli -``` - -# Testing the install script - -To test the install script, from a machine with docker: - -```console -docker build -t testclilinux -f scripts/Dockerfile-testInstall scripts -``` \ No newline at end of file diff --git a/MAINTAINERS b/MAINTAINERS index 5614d43f9..1a74da35c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26,6 +26,7 @@ "gtardif", "ndeloof" "chris-crone" + "ulyssessouza" ] [people] @@ -55,3 +56,8 @@ Name = "Djordje Lukic" Email = "djordje.lukic@docker.com" GitHub = "rumpl" + + [people.ulyssessouza] + Name = "Ulysses Souza" + Email = " 0 { - err := wsutil.WriteClientMessage(conn, ws.OpText, buffer) - if err != nil { - upstreamChannel <- err - return - } - } - } - }() - } - - for { - select { - case err := <-downstreamChannel: - return errors.Wrap(err, "failed to read input from container") - case err := <-upstreamChannel: - return errors.Wrap(err, "failed to send input to container") - } - } -} - -func getACIContainerLogs(ctx context.Context, aciContext store.AciContext, containerGroupName, containerName string, tail *int32) (string, error) { - containerClient, err := login.NewContainerClient(aciContext.SubscriptionID) - if err != nil { - return "", errors.Wrapf(err, "cannot get container client") - } - - logs, err := containerClient.ListLogs(ctx, aciContext.ResourceGroup, containerGroupName, containerName, tail) - if err != nil { - return "", fmt.Errorf("cannot get container logs: %v", err) - } - if logs.Content == nil { - return "", nil - } - return *logs.Content, err -} - -func streamLogs(ctx context.Context, aciContext store.AciContext, containerGroupName, containerName string, req containers.LogsRequest) error { - numLines := 0 - previousLogLines := "" - firstDisplay := true // optimization to exit sooner in cases like docker run hello-world, do not wait another 2 secs. - for { - select { - case <-ctx.Done(): - return nil - default: - logs, err := getACIContainerLogs(ctx, aciContext, containerGroupName, containerName, nil) - if err != nil { - return err - } - logLines := strings.Split(logs, "\n") - currentOutput := len(logLines) - - // Note: a backend should not do this normally, this breaks the log - // streaming over gRPC but this is the only thing we can do with - // the kind of logs ACI is giving us. Hopefully Azue will give us - // a real logs streaming api soon. - b := aec.EmptyBuilder - b = b.Up(uint(numLines)) - fmt.Fprint(req.Writer, b.Column(0).ANSI) - - numLines = getBacktrackLines(logLines, req.Width) - - for i := 0; i < currentOutput-1; i++ { - fmt.Fprintln(req.Writer, logLines[i]) - } - - if (firstDisplay || previousLogLines == logs) && !isContainerRunning(ctx, aciContext, containerGroupName, containerName) { - return nil - } - firstDisplay = false - previousLogLines = logs - - select { - case <-ctx.Done(): - return nil - case <-time.After(2 * time.Second): - } - } - } -} - -func isContainerRunning(ctx context.Context, aciContext store.AciContext, containerGroupName, containerName string) bool { - group, err := getACIContainerGroup(ctx, aciContext, containerGroupName) - if err != nil { - return false // group has disappeared - } - for _, container := range *group.Containers { - if *container.Name == containerName { - if convert.GetStatus(container, group) == convert.StatusRunning { - return true - } - } - } - return false -} - -func getBacktrackLines(lines []string, terminalWidth int) int { - if terminalWidth == 0 { // no terminal width has been set, do not divide by zero - return len(lines) - } - numLines := 0 - for i := 0; i < len(lines)-1; i++ { - numLines++ - if len(lines[i]) > terminalWidth { - numLines += len(lines[i]) / terminalWidth - } - } - - return numLines -} diff --git a/aci/aci_test.go b/aci/aci_test.go deleted file mode 100644 index 7ab6fe942..000000000 --- a/aci/aci_test.go +++ /dev/null @@ -1,28 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package aci - -import ( - "testing" - - "gotest.tools/v3/assert" -) - -func TestGetLinesWritten(t *testing.T) { - assert.Equal(t, 0, getBacktrackLines([]string{"Hello"}, 10)) - assert.Equal(t, 3, getBacktrackLines([]string{"Hello", "world"}, 2)) -} diff --git a/aci/backend.go b/aci/backend.go deleted file mode 100644 index ac782356c..000000000 --- a/aci/backend.go +++ /dev/null @@ -1,164 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package aci - -import ( - "strings" - - "github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2019-12-01/containerinstance" - "github.com/Azure/go-autorest/autorest/to" - "github.com/pkg/errors" - - "github.com/docker/compose-cli/aci/convert" - "github.com/docker/compose-cli/aci/login" - "github.com/docker/compose-cli/api/backend" - "github.com/docker/compose-cli/api/containers" - "github.com/docker/compose-cli/api/resources" - "github.com/docker/compose-cli/api/secrets" - "github.com/docker/compose-cli/api/volumes" - "github.com/docker/compose-cli/pkg/api" - - "github.com/docker/compose-cli/api/cloud" - apicontext "github.com/docker/compose-cli/api/context" - "github.com/docker/compose-cli/api/context/store" -) - -const ( - backendType = store.AciContextType - singleContainerTag = "docker-single-container" - composeContainerTag = "docker-compose-application" - dockerVolumeTag = "docker-volume" - composeContainerSeparator = "_" -) - -// LoginParams azure login options -type LoginParams struct { - TenantID string - ClientID string - ClientSecret string - CloudName string -} - -// Validate returns an error if options are not used properly -func (opts LoginParams) Validate() error { - if opts.ClientID != "" || opts.ClientSecret != "" { - if opts.ClientID == "" || opts.ClientSecret == "" || opts.TenantID == "" { - return errors.New("for Service Principal login, 3 options must be specified: --client-id, --client-secret and --tenant-id") - } - } - return nil -} - -func init() { - backend.Register(backendType, backendType, service, getCloudService) -} - -func service() (backend.Service, error) { - contextStore := store.Instance() - currentContext := apicontext.Current() - var aciContext store.AciContext - - if err := contextStore.GetEndpoint(currentContext, &aciContext); err != nil { - return nil, err - } - - return getAciAPIService(aciContext), nil -} - -func getCloudService() (cloud.Service, error) { - service, err := login.NewAzureLoginService() - if err != nil { - return nil, err - } - return &aciCloudService{ - loginService: service, - }, nil -} - -func getAciAPIService(aciCtx store.AciContext) *aciAPIService { - containerService := newContainerService(aciCtx) - composeService := newComposeService(aciCtx) - return &aciAPIService{ - aciContainerService: &containerService, - aciComposeService: &composeService, - aciVolumeService: &aciVolumeService{ - aciContext: aciCtx, - }, - aciResourceService: &aciResourceService{ - aciContext: aciCtx, - }, - } -} - -type aciAPIService struct { - *aciContainerService - *aciComposeService - *aciVolumeService - *aciResourceService -} - -func (a *aciAPIService) ContainerService() containers.Service { - return a.aciContainerService -} - -func (a *aciAPIService) ComposeService() api.Service { - return a.aciComposeService -} - -func (a *aciAPIService) SecretsService() secrets.Service { - // Not implemented on ACI - // Secrets are created and mounted in the container at it's creation and not stored on ACI - return nil -} - -func (a *aciAPIService) VolumeService() volumes.Service { - return a.aciVolumeService -} - -func (a *aciAPIService) ResourceService() resources.Service { - return a.aciResourceService -} - -func getContainerID(group containerinstance.ContainerGroup, container containerinstance.Container) string { - containerID := *group.Name + composeContainerSeparator + *container.Name - if _, ok := group.Tags[singleContainerTag]; ok { - containerID = *group.Name - } - return containerID -} - -func isContainerVisible(container containerinstance.Container, group containerinstance.ContainerGroup, showAll bool) bool { - return *container.Name == convert.ComposeDNSSidecarName || (!showAll && convert.GetStatus(container, group) != convert.StatusRunning) -} - -func addTag(groupDefinition *containerinstance.ContainerGroup, tagName string) { - if groupDefinition.Tags == nil { - groupDefinition.Tags = make(map[string]*string, 1) - } - groupDefinition.Tags[tagName] = to.StringPtr(tagName) -} - -func getGroupAndContainerName(containerID string) (string, string) { - tokens := strings.Split(containerID, composeContainerSeparator) - groupName := tokens[0] - containerName := groupName - if len(tokens) > 1 { - containerName = tokens[len(tokens)-1] - groupName = containerID[:len(containerID)-(len(containerName)+1)] - } - return groupName, containerName -} diff --git a/aci/backend_test.go b/aci/backend_test.go deleted file mode 100644 index 909171657..000000000 --- a/aci/backend_test.go +++ /dev/null @@ -1,165 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package aci - -import ( - "context" - "testing" - - "github.com/stretchr/testify/mock" - "gotest.tools/v3/assert" - - "github.com/docker/compose-cli/aci/login" - "github.com/docker/compose-cli/api/containers" - "golang.org/x/oauth2" -) - -func TestGetContainerName(t *testing.T) { - group, container := getGroupAndContainerName("docker1234") - assert.Equal(t, group, "docker1234") - assert.Equal(t, container, "docker1234") - - group, container = getGroupAndContainerName("compose_service1") - assert.Equal(t, group, "compose") - assert.Equal(t, container, "service1") - - group, container = getGroupAndContainerName("compose_stack_service1") - assert.Equal(t, group, "compose_stack") - assert.Equal(t, container, "service1") -} - -func TestErrorMessageDeletingContainerFromComposeApplication(t *testing.T) { - service := aciContainerService{} - err := service.Delete(context.TODO(), "compose-app_service1", containers.DeleteRequest{Force: false}) - assert.Error(t, err, "cannot delete service \"service1\" from compose application \"compose-app\", you can delete the entire compose app with docker compose down --project-name compose-app") -} - -func TestErrorMessageRunSingleContainerNameWithComposeSeparator(t *testing.T) { - service := aciContainerService{} - err := service.Run(context.TODO(), containers.ContainerConfig{ID: "container_name"}) - assert.Error(t, err, "invalid container name. ACI container name cannot include \"_\"") -} - -func TestVerifyCommand(t *testing.T) { - err := verifyExecCommand("command") // Command without an argument - assert.NilError(t, err) - err = verifyExecCommand("command argument") // Command with argument - assert.Error(t, err, "ACI exec command does not accept arguments to the command. "+ - "Only the binary should be specified") -} - -func TestLoginParamsValidate(t *testing.T) { - err := LoginParams{ - ClientID: "someID", - }.Validate() - assert.Error(t, err, "for Service Principal login, 3 options must be specified: --client-id, --client-secret and --tenant-id") - - err = LoginParams{ - ClientSecret: "someSecret", - }.Validate() - assert.Error(t, err, "for Service Principal login, 3 options must be specified: --client-id, --client-secret and --tenant-id") - - err = LoginParams{}.Validate() - assert.NilError(t, err) - - err = LoginParams{ - TenantID: "tenant", - }.Validate() - assert.NilError(t, err) -} - -func TestLoginServicePrincipal(t *testing.T) { - loginService := mockLoginService{} - loginService.On("LoginServicePrincipal", "someID", "secret", "tenant", "someCloud").Return(nil) - loginBackend := aciCloudService{ - loginService: &loginService, - } - - err := loginBackend.Login(context.Background(), LoginParams{ - ClientID: "someID", - ClientSecret: "secret", - TenantID: "tenant", - CloudName: "someCloud", - }) - - assert.NilError(t, err) -} - -func TestLoginWithTenant(t *testing.T) { - loginService := mockLoginService{} - ctx := context.Background() - loginService.On("Login", ctx, "tenant", "someCloud").Return(nil) - loginBackend := aciCloudService{ - loginService: &loginService, - } - - err := loginBackend.Login(ctx, LoginParams{ - TenantID: "tenant", - CloudName: "someCloud", - }) - - assert.NilError(t, err) -} - -func TestLoginWithoutTenant(t *testing.T) { - loginService := mockLoginService{} - ctx := context.Background() - loginService.On("Login", ctx, "", "someCloud").Return(nil) - loginBackend := aciCloudService{ - loginService: &loginService, - } - - err := loginBackend.Login(ctx, LoginParams{ - CloudName: "someCloud", - }) - - assert.NilError(t, err) -} - -type mockLoginService struct { - mock.Mock -} - -func (s *mockLoginService) Login(ctx context.Context, requestedTenantID string, cloudEnvironment string) error { - args := s.Called(ctx, requestedTenantID, cloudEnvironment) - return args.Error(0) -} - -func (s *mockLoginService) LoginServicePrincipal(clientID string, clientSecret string, tenantID string, cloudEnvironment string) error { - args := s.Called(clientID, clientSecret, tenantID, cloudEnvironment) - return args.Error(0) -} - -func (s *mockLoginService) Logout(ctx context.Context) error { - args := s.Called(ctx) - return args.Error(0) -} - -func (s *mockLoginService) GetTenantID() (string, error) { - args := s.Called() - return args.String(0), args.Error(1) -} - -func (s *mockLoginService) GetCloudEnvironment() (login.CloudEnvironment, error) { - args := s.Called() - return args.Get(0).(login.CloudEnvironment), args.Error(1) -} - -func (s *mockLoginService) GetValidToken() (oauth2.Token, string, error) { - args := s.Called() - return args.Get(0).(oauth2.Token), args.String(1), args.Error(2) -} diff --git a/aci/cloud.go b/aci/cloud.go deleted file mode 100644 index d9b13b1a0..000000000 --- a/aci/cloud.go +++ /dev/null @@ -1,53 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package aci - -import ( - "context" - - "github.com/pkg/errors" - - "github.com/docker/compose-cli/aci/login" -) - -type aciCloudService struct { - loginService login.AzureLoginService -} - -func (cs *aciCloudService) Login(ctx context.Context, params interface{}) error { - opts, ok := params.(LoginParams) - if !ok { - return errors.New("could not read Azure LoginParams struct from generic parameter") - } - if opts.CloudName == "" { - opts.CloudName = login.AzurePublicCloudName - } - if opts.ClientID != "" { - return cs.loginService.LoginServicePrincipal(opts.ClientID, opts.ClientSecret, opts.TenantID, opts.CloudName) - } - return cs.loginService.Login(ctx, opts.TenantID, opts.CloudName) -} - -func (cs *aciCloudService) Logout(ctx context.Context) error { - return cs.loginService.Logout(ctx) -} - -func (cs *aciCloudService) CreateContextData(ctx context.Context, params interface{}) (interface{}, string, error) { - contextHelper := newContextCreateHelper() - createOpts := params.(ContextParams) - return contextHelper.createContextData(ctx, createOpts) -} diff --git a/aci/compose.go b/aci/compose.go deleted file mode 100644 index e3a831354..000000000 --- a/aci/compose.go +++ /dev/null @@ -1,266 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package aci - -import ( - "context" - "fmt" - "net/http" - - "github.com/compose-spec/compose-go/types" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - - "github.com/docker/compose-cli/aci/convert" - "github.com/docker/compose-cli/aci/login" - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/progress" - "github.com/docker/compose-cli/utils/formatter" -) - -type aciComposeService struct { - ctx store.AciContext - storageLogin login.StorageLoginImpl -} - -func newComposeService(ctx store.AciContext) aciComposeService { - return aciComposeService{ - ctx: ctx, - storageLogin: login.StorageLoginImpl{AciContext: ctx}, - } -} - -func (cs *aciComposeService) Build(ctx context.Context, project *types.Project, options api.BuildOptions) error { - return api.ErrNotImplemented -} - -func (cs *aciComposeService) Push(ctx context.Context, project *types.Project, options api.PushOptions) error { - return api.ErrNotImplemented -} - -func (cs *aciComposeService) Pull(ctx context.Context, project *types.Project, options api.PullOptions) error { - return api.ErrNotImplemented -} - -func (cs *aciComposeService) Create(ctx context.Context, project *types.Project, opts api.CreateOptions) error { - return api.ErrNotImplemented -} - -func (cs *aciComposeService) Start(ctx context.Context, project *types.Project, options api.StartOptions) error { - return api.ErrNotImplemented -} - -func (cs *aciComposeService) Restart(ctx context.Context, project *types.Project, options api.RestartOptions) error { - return api.ErrNotImplemented -} - -func (cs *aciComposeService) Stop(ctx context.Context, project *types.Project, options api.StopOptions) error { - return api.ErrNotImplemented -} - -func (cs *aciComposeService) Pause(ctx context.Context, project string, options api.PauseOptions) error { - return api.ErrNotImplemented -} - -func (cs *aciComposeService) UnPause(ctx context.Context, project string, options api.PauseOptions) error { - return api.ErrNotImplemented -} - -func (cs *aciComposeService) Copy(ctx context.Context, project *types.Project, options api.CopyOptions) error { - return api.ErrNotImplemented -} - -func (cs *aciComposeService) Up(ctx context.Context, project *types.Project, options api.UpOptions) error { - return progress.Run(ctx, func(ctx context.Context) error { - return cs.up(ctx, project) - }) -} - -func (cs *aciComposeService) up(ctx context.Context, project *types.Project) error { - logrus.Debugf("Up on project with name %q", project.Name) - - if err := autocreateFileshares(ctx, project); err != nil { - return err - } - - groupDefinition, err := convert.ToContainerGroup(ctx, cs.ctx, *project, cs.storageLogin) - if err != nil { - return err - } - - addTag(&groupDefinition, composeContainerTag) - return createOrUpdateACIContainers(ctx, cs.ctx, groupDefinition) -} - -func (cs aciComposeService) warnKeepVolumeOnDown(ctx context.Context, projectName string) error { - cgClient, err := login.NewContainerGroupsClient(cs.ctx.SubscriptionID) - if err != nil { - return err - } - cg, err := cgClient.Get(ctx, cs.ctx.ResourceGroup, projectName) - if err != nil { - return err - } - if cg.Volumes == nil { - return nil - } - for _, v := range *cg.Volumes { - if v.AzureFile == nil || v.AzureFile.StorageAccountName == nil || v.AzureFile.ShareName == nil { - continue - } - fmt.Printf("WARNING: fileshare \"%s/%s\" will NOT be deleted. Use 'docker volume rm' if you want to delete this volume\n", - *v.AzureFile.StorageAccountName, *v.AzureFile.ShareName) - } - return nil -} - -func (cs *aciComposeService) Down(ctx context.Context, projectName string, options api.DownOptions) error { - if options.Volumes { - return errors.Wrap(api.ErrNotImplemented, "--volumes option is not supported on ACI") - } - if options.Images != "" { - return errors.Wrap(api.ErrNotImplemented, "--rmi option is not supported on ACI") - } - return progress.Run(ctx, func(ctx context.Context) error { - logrus.Debugf("Down on project with name %q", projectName) - - if err := cs.warnKeepVolumeOnDown(ctx, projectName); err != nil { - return err - } - - cg, err := deleteACIContainerGroup(ctx, cs.ctx, projectName) - if err != nil { - return err - } - if cg.IsHTTPStatus(http.StatusNoContent) { - return api.ErrNotFound - } - - return err - }) -} - -func (cs *aciComposeService) Ps(ctx context.Context, projectName string, options api.PsOptions) ([]api.ContainerSummary, error) { - groupsClient, err := login.NewContainerGroupsClient(cs.ctx.SubscriptionID) - if err != nil { - return nil, err - } - - group, err := groupsClient.Get(ctx, cs.ctx.ResourceGroup, projectName) - if err != nil { - return nil, err - } - - if group.Containers == nil || len(*group.Containers) == 0 { - return nil, fmt.Errorf("no containers found in ACI container group %s", projectName) - } - - res := []api.ContainerSummary{} - for _, container := range *group.Containers { - if isContainerVisible(container, group, false) { - continue - } - var publishers []api.PortPublisher - urls := formatter.PortsToStrings(convert.ToPorts(group.IPAddress, *container.Ports), convert.FQDN(group, cs.ctx.Location)) - for i, p := range *container.Ports { - publishers = append(publishers, api.PortPublisher{ - URL: urls[i], - TargetPort: int(*p.Port), - PublishedPort: int(*p.Port), - Protocol: string(p.Protocol), - }) - } - id := getContainerID(group, container) - res = append(res, api.ContainerSummary{ - ID: id, - Name: id, - Project: projectName, - Service: *container.Name, - State: convert.GetStatus(container, group), - Publishers: publishers, - }) - } - return res, nil -} - -func (cs *aciComposeService) List(ctx context.Context, opts api.ListOptions) ([]api.Stack, error) { - containerGroups, err := getACIContainerGroups(ctx, cs.ctx.SubscriptionID, cs.ctx.ResourceGroup) - if err != nil { - return nil, err - } - - var stacks []api.Stack - for _, group := range containerGroups { - if _, found := group.Tags[composeContainerTag]; !found { - continue - } - state := api.RUNNING - for _, container := range *group.ContainerGroupProperties.Containers { - containerState := convert.GetStatus(container, group) - if containerState != api.RUNNING { - state = containerState - break - } - } - stacks = append(stacks, api.Stack{ - ID: *group.ID, - Name: *group.Name, - Status: state, - }) - } - return stacks, nil -} - -func (cs *aciComposeService) Logs(ctx context.Context, projectName string, consumer api.LogConsumer, options api.LogOptions) error { - return api.ErrNotImplemented -} - -func (cs *aciComposeService) Convert(ctx context.Context, project *types.Project, options api.ConvertOptions) ([]byte, error) { - return nil, api.ErrNotImplemented -} - -func (cs *aciComposeService) Kill(ctx context.Context, project *types.Project, options api.KillOptions) error { - return api.ErrNotImplemented -} - -func (cs *aciComposeService) RunOneOffContainer(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) { - return 0, api.ErrNotImplemented -} - -func (cs *aciComposeService) Remove(ctx context.Context, project *types.Project, options api.RemoveOptions) error { - return api.ErrNotImplemented -} - -func (cs *aciComposeService) Exec(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) { - return 0, api.ErrNotImplemented -} -func (cs *aciComposeService) Top(ctx context.Context, projectName string, services []string) ([]api.ContainerProcSummary, error) { - return nil, api.ErrNotImplemented -} - -func (cs *aciComposeService) Events(ctx context.Context, project string, options api.EventsOptions) error { - return api.ErrNotImplemented -} - -func (cs *aciComposeService) Port(ctx context.Context, project string, service string, port int, options api.PortOptions) (string, int, error) { - return "", 0, api.ErrNotImplemented -} - -func (cs *aciComposeService) Images(ctx context.Context, projectName string, options api.ImagesOptions) ([]api.ImageSummary, error) { - return nil, api.ErrNotImplemented -} diff --git a/aci/containers.go b/aci/containers.go deleted file mode 100644 index 60407e2b0..000000000 --- a/aci/containers.go +++ /dev/null @@ -1,264 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package aci - -import ( - "context" - "fmt" - "net/http" - "strconv" - "strings" - - "github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2019-12-01/containerinstance" - "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/to" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - - "github.com/docker/compose-cli/aci/convert" - "github.com/docker/compose-cli/aci/login" - "github.com/docker/compose-cli/api/containers" - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/pkg/api" -) - -type aciContainerService struct { - ctx store.AciContext - storageLogin login.StorageLoginImpl -} - -func newContainerService(ctx store.AciContext) aciContainerService { - return aciContainerService{ - ctx: ctx, - storageLogin: login.StorageLoginImpl{AciContext: ctx}, - } -} - -func (cs *aciContainerService) List(ctx context.Context, all bool) ([]containers.Container, error) { - containerGroups, err := getACIContainerGroups(ctx, cs.ctx.SubscriptionID, cs.ctx.ResourceGroup) - if err != nil { - return nil, err - } - res := []containers.Container{} - for _, group := range containerGroups { - if group.Containers == nil || len(*group.Containers) == 0 { - return nil, fmt.Errorf("no containers found in ACI container group %s", *group.Name) - } - - for _, container := range *group.Containers { - if isContainerVisible(container, group, all) { - continue - } - c := convert.ContainerGroupToContainer(getContainerID(group, container), group, container, cs.ctx.Location) - res = append(res, c) - } - } - return res, nil -} - -func (cs *aciContainerService) Run(ctx context.Context, r containers.ContainerConfig) error { - if strings.Contains(r.ID, composeContainerSeparator) { - return fmt.Errorf("invalid container name. ACI container name cannot include %q", composeContainerSeparator) - } - - project, err := convert.ContainerToComposeProject(r) - if err != nil { - return err - } - - logrus.Debugf("Running container %q with name %q", r.Image, r.ID) - groupDefinition, err := convert.ToContainerGroup(ctx, cs.ctx, project, cs.storageLogin) - if err != nil { - return err - } - addTag(&groupDefinition, singleContainerTag) - - return createACIContainers(ctx, cs.ctx, groupDefinition) -} - -func (cs *aciContainerService) Start(ctx context.Context, containerID string) error { - groupName, containerName := getGroupAndContainerName(containerID) - if groupName != containerID { - msg := "cannot start specified service %q from compose application %q, you can update and restart the entire compose app with docker compose up --project-name %s" - return fmt.Errorf(msg, containerName, groupName, groupName) - } - - containerGroupsClient, err := login.NewContainerGroupsClient(cs.ctx.SubscriptionID) - if err != nil { - return err - } - - future, err := containerGroupsClient.Start(ctx, cs.ctx.ResourceGroup, containerName) - if err != nil { - var aerr autorest.DetailedError - if ok := errors.As(err, &aerr); ok { - if aerr.StatusCode == http.StatusNotFound { - return api.ErrNotFound - } - } - return err - } - - return future.WaitForCompletionRef(ctx, containerGroupsClient.Client) -} - -func (cs *aciContainerService) Stop(ctx context.Context, containerID string, timeout *uint32) error { - if timeout != nil && *timeout != uint32(0) { - return fmt.Errorf("the ACI integration does not support setting a timeout to stop a container before killing it") - } - groupName, containerName := getGroupAndContainerName(containerID) - if groupName != containerID { - msg := "cannot stop service %q from compose application %q, you can stop the entire compose app with docker stop %s" - return fmt.Errorf(msg, containerName, groupName, groupName) - } - return stopACIContainerGroup(ctx, cs.ctx, groupName) -} - -func (cs *aciContainerService) Kill(ctx context.Context, containerID string, _ string) error { - groupName, containerName := getGroupAndContainerName(containerID) - if groupName != containerID { - msg := "cannot kill service %q from compose application %q, you can kill the entire compose app with docker kill %s" - return fmt.Errorf(msg, containerName, groupName, groupName) - } - return stopACIContainerGroup(ctx, cs.ctx, groupName) // As ACI doesn't have a kill command, we are using the stop implementation instead -} - -func (cs *aciContainerService) Exec(ctx context.Context, name string, request containers.ExecRequest) error { - err := verifyExecCommand(request.Command) - if err != nil { - return err - } - groupName, containerAciName := getGroupAndContainerName(name) - containerExecResponse, err := execACIContainer(ctx, cs.ctx, request.Command, groupName, containerAciName) - if err != nil { - return err - } - - return exec( - context.Background(), - *containerExecResponse.WebSocketURI, - *containerExecResponse.Password, - request, - ) -} - -func verifyExecCommand(command string) error { - tokens := strings.Split(command, " ") - if len(tokens) > 1 { - return errors.New("ACI exec command does not accept arguments to the command. " + - "Only the binary should be specified") - } - return nil -} - -func (cs *aciContainerService) Logs(ctx context.Context, containerName string, req containers.LogsRequest) error { - groupName, containerAciName := getGroupAndContainerName(containerName) - var tail *int32 - - if req.Follow { - return streamLogs(ctx, cs.ctx, groupName, containerAciName, req) - } - - if req.Tail != "all" { - reqTail, err := strconv.Atoi(req.Tail) - if err != nil { - return err - } - i32 := int32(reqTail) - tail = &i32 - } - - logs, err := getACIContainerLogs(ctx, cs.ctx, groupName, containerAciName, tail) - if err != nil { - return err - } - - _, err = fmt.Fprint(req.Writer, logs) - return err -} - -func (cs *aciContainerService) Delete(ctx context.Context, containerID string, request containers.DeleteRequest) error { - groupName, containerName := getGroupAndContainerName(containerID) - if groupName != containerID { - msg := "cannot delete service %q from compose application %q, you can delete the entire compose app with docker compose down --project-name %s" - return fmt.Errorf(msg, containerName, groupName, groupName) - } - - if !request.Force { - containerGroupsClient, err := login.NewContainerGroupsClient(cs.ctx.SubscriptionID) - if err != nil { - return err - } - - cg, err := containerGroupsClient.Get(ctx, cs.ctx.ResourceGroup, groupName) - if err != nil { - if cg.StatusCode == http.StatusNotFound { - return api.ErrNotFound - } - return err - } - - for _, container := range *cg.Containers { - status := convert.GetStatus(container, cg) - - if status == convert.StatusRunning { - return api.ErrForbidden - } - } - } - - cg, err := deleteACIContainerGroup(ctx, cs.ctx, groupName) - // Delete returns `StatusNoContent` if the group is not found - if cg.IsHTTPStatus(http.StatusNoContent) { - return api.ErrNotFound - } - if err != nil { - return err - } - - return err -} - -func (cs *aciContainerService) Inspect(ctx context.Context, containerID string) (containers.Container, error) { - groupName, containerName := getGroupAndContainerName(containerID) - if containerID == "" { - return containers.Container{}, errors.New("cannot inspect empty container ID") - } - - cg, err := getACIContainerGroup(ctx, cs.ctx, groupName) - if err != nil { - return containers.Container{}, err - } - if cg.IsHTTPStatus(http.StatusNoContent) || cg.ContainerGroupProperties == nil || cg.ContainerGroupProperties.Containers == nil { - return containers.Container{}, api.ErrNotFound - } - - var cc containerinstance.Container - var found = false - for _, c := range *cg.Containers { - if to.String(c.Name) == containerName { - cc = c - found = true - break - } - } - if !found { - return containers.Container{}, api.ErrNotFound - } - - return convert.ContainerGroupToContainer(containerID, cg, cc, cs.ctx.Location), nil -} diff --git a/aci/context.go b/aci/context.go deleted file mode 100644 index 42f7b6274..000000000 --- a/aci/context.go +++ /dev/null @@ -1,184 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package aci - -import ( - "context" - "fmt" - "os" - - "github.com/AlecAivazis/survey/v2/terminal" - "github.com/Azure/azure-sdk-for-go/profiles/preview/preview/subscription/mgmt/subscription" - "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-05-01/resources" - "github.com/hashicorp/go-uuid" - "github.com/pkg/errors" - - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/prompt" -) - -// ContextParams options for creating ACI context -type ContextParams struct { - Description string - Location string - SubscriptionID string - ResourceGroup string -} - -// ErrSubscriptionNotFound is returned when a required subscription is not found -var ErrSubscriptionNotFound = errors.Wrapf(api.ErrNotFound, "subscription") - -// IsSubscriptionNotFoundError returns true if the unwrapped error is IsSubscriptionNotFoundError -func IsSubscriptionNotFoundError(err error) bool { - return errors.Is(err, ErrSubscriptionNotFound) -} - -type contextCreateACIHelper struct { - selector prompt.UI - resourceGroupHelper ResourceGroupHelper -} - -func newContextCreateHelper() contextCreateACIHelper { - return contextCreateACIHelper{ - selector: prompt.User{}, - resourceGroupHelper: aciResourceGroupHelperImpl{}, - } -} - -func (helper contextCreateACIHelper) createContextData(ctx context.Context, opts ContextParams) (interface{}, string, error) { - subs, err := helper.resourceGroupHelper.GetSubscriptionIDs(ctx) - if err != nil { - return nil, "", err - } - subscriptionID := "" - if opts.SubscriptionID != "" { - for _, sub := range subs { - if *sub.SubscriptionID == opts.SubscriptionID { - subscriptionID = opts.SubscriptionID - } - } - if subscriptionID == "" { - return nil, "", ErrSubscriptionNotFound - } - } else { - subscriptionID, err = helper.chooseSub(subs) - if err != nil { - return nil, "", err - } - } - - var group resources.Group - if opts.ResourceGroup != "" { - group, err = helper.resourceGroupHelper.GetGroup(ctx, subscriptionID, opts.ResourceGroup) - if err != nil { - return nil, "", errors.Wrapf(err, "Could not find resource group %q", opts.ResourceGroup) - } - } else { - groups, err := helper.resourceGroupHelper.ListGroups(ctx, subscriptionID) - if err != nil { - return nil, "", err - } - group, err = helper.chooseGroup(ctx, subscriptionID, opts, groups) - if err != nil { - return nil, "", err - } - } - - location := opts.Location - if opts.Location == "" { - location = *group.Location - } - - description := fmt.Sprintf("%s@%s", *group.Name, location) - if opts.Description != "" { - description = fmt.Sprintf("%s (%s)", opts.Description, description) - } - - return store.AciContext{ - SubscriptionID: subscriptionID, - Location: location, - ResourceGroup: *group.Name, - }, description, nil -} - -func (helper contextCreateACIHelper) createGroup(ctx context.Context, subscriptionID, location string) (resources.Group, error) { - if location == "" { - location = "eastus" - } - gid, err := uuid.GenerateUUID() - if err != nil { - return resources.Group{}, err - } - g, err := helper.resourceGroupHelper.CreateOrUpdate(ctx, subscriptionID, gid, resources.Group{ - Location: &location, - }) - if err != nil { - return resources.Group{}, err - } - - fmt.Printf("Resource group %q (%s) created\n", *g.Name, *g.Location) - - return g, nil -} - -func (helper contextCreateACIHelper) chooseGroup(ctx context.Context, subscriptionID string, opts ContextParams, groups []resources.Group) (resources.Group, error) { - groupNames := []string{"create a new resource group"} - for _, g := range groups { - groupNames = append(groupNames, fmt.Sprintf("%s (%s)", *g.Name, *g.Location)) - } - - group, err := helper.selector.Select("Select a resource group", groupNames) - if err != nil { - if err == terminal.InterruptErr { - return resources.Group{}, api.ErrCanceled - } - - return resources.Group{}, err - } - - if group == 0 { - return helper.createGroup(ctx, subscriptionID, opts.Location) - } - - return groups[group-1], nil -} - -func (helper contextCreateACIHelper) chooseSub(subs []subscription.Model) (string, error) { - if len(subs) == 1 { - sub := subs[0] - fmt.Println("Using only available subscription : " + display(sub)) - return *sub.SubscriptionID, nil - } - var options []string - for _, sub := range subs { - options = append(options, display(sub)) - } - selected, err := helper.selector.Select("Select a subscription ID", options) - if err != nil { - if err == terminal.InterruptErr { - os.Exit(0) - } - return "", err - } - - return *subs[selected].SubscriptionID, nil -} - -func display(sub subscription.Model) string { - return fmt.Sprintf("%s (%s)", *sub.DisplayName, *sub.SubscriptionID) -} diff --git a/aci/context_test.go b/aci/context_test.go deleted file mode 100644 index 51a384e3d..000000000 --- a/aci/context_test.go +++ /dev/null @@ -1,272 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package aci - -import ( - "context" - "testing" - - "github.com/Azure/azure-sdk-for-go/profiles/2019-03-01/resources/mgmt/resources" - "github.com/Azure/azure-sdk-for-go/profiles/preview/preview/subscription/mgmt/subscription" - "github.com/Azure/go-autorest/autorest/to" - "github.com/pkg/errors" - "github.com/stretchr/testify/mock" - "gotest.tools/v3/assert" - "gotest.tools/v3/assert/cmp" - - "github.com/docker/compose-cli/api/context/store" -) - -type contextMocks struct { - userPrompt *mockUserPrompt - resourceGroupHelper *MockResourceGroupHelper - contextCreateHelper contextCreateACIHelper -} - -func testContextMocks() contextMocks { - mockUserPrompt := &mockUserPrompt{} - mockResourceGroupHelper := &MockResourceGroupHelper{} - contextCreateHelper := contextCreateACIHelper{ - mockUserPrompt, - mockResourceGroupHelper, - } - return contextMocks{mockUserPrompt, mockResourceGroupHelper, contextCreateHelper} -} - -func TestCreateSpecifiedSubscriptionAndGroup(t *testing.T) { - ctx := context.TODO() - opts := options("1234", "myResourceGroup") - m := testContextMocks() - m.resourceGroupHelper.On("GetSubscriptionIDs", ctx).Return([]subscription.Model{subModel("1234", "Subscription1")}, nil) - m.resourceGroupHelper.On("GetGroup", ctx, "1234", "myResourceGroup").Return(group("myResourceGroup", "eastus"), nil) - - data, description, err := m.contextCreateHelper.createContextData(ctx, opts) - assert.NilError(t, err) - assert.Equal(t, description, "myResourceGroup@eastus") - assert.DeepEqual(t, data, aciContext("1234", "myResourceGroup", "eastus")) -} - -func TestErrorOnNonExistentResourceGroup(t *testing.T) { - ctx := context.TODO() - opts := options("1234", "myResourceGroup") - notFoundError := errors.New(`Not Found: "myResourceGroup"`) - m := testContextMocks() - m.resourceGroupHelper.On("GetSubscriptionIDs", ctx).Return([]subscription.Model{subModel("1234", "Subscription1")}, nil) - m.resourceGroupHelper.On("GetGroup", ctx, "1234", "myResourceGroup").Return(resources.Group{}, notFoundError) - - data, description, err := m.contextCreateHelper.createContextData(ctx, opts) - assert.Assert(t, cmp.Nil(data)) - assert.Equal(t, description, "") - assert.Error(t, err, "Could not find resource group \"myResourceGroup\": Not Found: \"myResourceGroup\"") -} - -func TestErrorOnNonExistentSubscriptionID(t *testing.T) { - ctx := context.TODO() - opts := options("otherSubscription", "myResourceGroup") - m := testContextMocks() - m.resourceGroupHelper.On("GetSubscriptionIDs", ctx).Return([]subscription.Model{subModel("1234", "Subscription1")}, nil) - - data, description, err := m.contextCreateHelper.createContextData(ctx, opts) - assert.Assert(t, cmp.Nil(data)) - assert.Equal(t, description, "") - assert.Assert(t, err == ErrSubscriptionNotFound) -} - -func TestCreateNewResourceGroup(t *testing.T) { - ctx := context.TODO() - opts := options("1234", "") - m := testContextMocks() - m.resourceGroupHelper.On("GetSubscriptionIDs", ctx).Return([]subscription.Model{subModel("1234", "Subscription1")}, nil) - m.resourceGroupHelper.On("GetGroup", ctx, "1234", "myResourceGroup").Return(group("myResourceGroup", "eastus"), nil) - - selectOptions := []string{"create a new resource group", "group1 (eastus)", "group2 (westeurope)"} - m.userPrompt.On("Select", "Select a resource group", selectOptions).Return(0, nil) - m.resourceGroupHelper.On("CreateOrUpdate", ctx, "1234", mock.AnythingOfType("string"), mock.AnythingOfType("resources.Group")).Return(group("newResourceGroup", "eastus"), nil) - m.resourceGroupHelper.On("ListGroups", ctx, "1234").Return([]resources.Group{ - group("group1", "eastus"), - group("group2", "westeurope"), - }, nil) - - data, description, err := m.contextCreateHelper.createContextData(ctx, opts) - assert.NilError(t, err) - assert.Equal(t, description, "newResourceGroup@eastus") - assert.DeepEqual(t, data, aciContext("1234", "newResourceGroup", "eastus")) -} - -func TestCreateNewResourceGroupWithSpecificLocation(t *testing.T) { - ctx := context.TODO() - opts := options("1234", "") - opts.Location = "eastus2" - m := testContextMocks() - m.resourceGroupHelper.On("GetSubscriptionIDs", ctx).Return([]subscription.Model{subModel("1234", "Subscription1")}, nil) - m.resourceGroupHelper.On("GetGroup", ctx, "1234", "myResourceGroup").Return(group("myResourceGroup", "eastus"), nil) - - selectOptions := []string{"create a new resource group", "group1 (eastus)", "group2 (westeurope)"} - m.userPrompt.On("Select", "Select a resource group", selectOptions).Return(0, nil) - m.resourceGroupHelper.On("CreateOrUpdate", ctx, "1234", mock.AnythingOfType("string"), mock.AnythingOfType("resources.Group")).Return(group("newResourceGroup", "eastus"), nil) - m.resourceGroupHelper.On("ListGroups", ctx, "1234").Return([]resources.Group{ - group("group1", "eastus"), - group("group2", "westeurope"), - }, nil) - - data, description, err := m.contextCreateHelper.createContextData(ctx, opts) - assert.NilError(t, err) - assert.Equal(t, description, "newResourceGroup@eastus2") - assert.DeepEqual(t, data, aciContext("1234", "newResourceGroup", "eastus2")) -} - -func TestSelectExistingResourceGroup(t *testing.T) { - ctx := context.TODO() - opts := options("1234", "") - selectOptions := []string{"create a new resource group", "group1 (eastus)", "group2 (westeurope)"} - m := testContextMocks() - m.resourceGroupHelper.On("GetSubscriptionIDs", ctx).Return([]subscription.Model{subModel("1234", "Subscription1")}, nil) - m.userPrompt.On("Select", "Select a resource group", selectOptions).Return(2, nil) - m.resourceGroupHelper.On("ListGroups", ctx, "1234").Return([]resources.Group{ - group("group1", "eastus"), - group("group2", "westeurope"), - }, nil) - - data, description, err := m.contextCreateHelper.createContextData(ctx, opts) - assert.NilError(t, err) - assert.Equal(t, description, "group2@westeurope") - assert.DeepEqual(t, data, aciContext("1234", "group2", "westeurope")) -} - -func TestSelectSingleSubscriptionIdAndExistingResourceGroup(t *testing.T) { - ctx := context.TODO() - opts := options("", "") - m := testContextMocks() - m.resourceGroupHelper.On("GetSubscriptionIDs", ctx).Return([]subscription.Model{subModel("123456", "Subscription1")}, nil) - - selectOptions := []string{"create a new resource group", "group1 (eastus)", "group2 (westeurope)"} - m.userPrompt.On("Select", "Select a resource group", selectOptions).Return(2, nil) - m.resourceGroupHelper.On("ListGroups", ctx, "123456").Return([]resources.Group{ - group("group1", "eastus"), - group("group2", "westeurope"), - }, nil) - - data, description, err := m.contextCreateHelper.createContextData(ctx, opts) - assert.NilError(t, err) - assert.Equal(t, description, "group2@westeurope") - assert.DeepEqual(t, data, aciContext("123456", "group2", "westeurope")) -} - -func TestSelectSubscriptionIdAndExistingResourceGroup(t *testing.T) { - ctx := context.TODO() - opts := options("", "") - sub1 := subModel("1234", "Subscription1") - sub2 := subModel("5678", "Subscription2") - - m := testContextMocks() - m.resourceGroupHelper.On("GetSubscriptionIDs", ctx).Return([]subscription.Model{sub1, sub2}, nil) - - selectOptions := []string{"Subscription1 (1234)", "Subscription2 (5678)"} - m.userPrompt.On("Select", "Select a subscription ID", selectOptions).Return(1, nil) - selectOptions = []string{"create a new resource group", "group1 (eastus)", "group2 (westeurope)"} - m.userPrompt.On("Select", "Select a resource group", selectOptions).Return(2, nil) - m.resourceGroupHelper.On("ListGroups", ctx, "5678").Return([]resources.Group{ - group("group1", "eastus"), - group("group2", "westeurope"), - }, nil) - - data, description, err := m.contextCreateHelper.createContextData(ctx, opts) - assert.NilError(t, err) - assert.Equal(t, description, "group2@westeurope") - assert.DeepEqual(t, data, aciContext("5678", "group2", "westeurope")) -} - -func subModel(subID string, display string) subscription.Model { - return subscription.Model{ - SubscriptionID: to.StringPtr(subID), - DisplayName: to.StringPtr(display), - } -} - -func group(groupName string, location string) resources.Group { - return resources.Group{ - Name: to.StringPtr(groupName), - Location: to.StringPtr(location), - } -} - -func aciContext(subscriptionID string, resourceGroupName string, location string) store.AciContext { - return store.AciContext{ - SubscriptionID: subscriptionID, - Location: location, - ResourceGroup: resourceGroupName, - } -} - -func options(subscriptionID string, resourceGroupName string) ContextParams { - return ContextParams{ - SubscriptionID: subscriptionID, - ResourceGroup: resourceGroupName, - } -} - -type mockUserPrompt struct { - mock.Mock -} - -func (s *mockUserPrompt) Select(message string, options []string) (int, error) { - args := s.Called(message, options) - return args.Int(0), args.Error(1) -} -func (s *mockUserPrompt) Confirm(message string, defaultValue bool) (bool, error) { - args := s.Called(message, options) - return args.Bool(0), args.Error(1) -} - -func (s *mockUserPrompt) Input(message string, defaultValue string) (string, error) { - args := s.Called(message, options) - return args.String(0), args.Error(1) -} - -func (s *mockUserPrompt) Password(message string) (string, error) { - args := s.Called(message, options) - return args.String(0), args.Error(1) -} - -type MockResourceGroupHelper struct { - mock.Mock -} - -func (s *MockResourceGroupHelper) GetSubscriptionIDs(ctx context.Context) ([]subscription.Model, error) { - args := s.Called(ctx) - return args.Get(0).([]subscription.Model), args.Error(1) -} - -func (s *MockResourceGroupHelper) ListGroups(ctx context.Context, subscriptionID string) ([]resources.Group, error) { - args := s.Called(ctx, subscriptionID) - return args.Get(0).([]resources.Group), args.Error(1) -} - -func (s *MockResourceGroupHelper) GetGroup(ctx context.Context, subscriptionID string, groupName string) (resources.Group, error) { - args := s.Called(ctx, subscriptionID, groupName) - return args.Get(0).(resources.Group), args.Error(1) -} - -func (s *MockResourceGroupHelper) CreateOrUpdate(ctx context.Context, subscriptionID string, resourceGroupName string, parameters resources.Group) (result resources.Group, err error) { - args := s.Called(ctx, subscriptionID, resourceGroupName, parameters) - return args.Get(0).(resources.Group), args.Error(1) -} - -func (s *MockResourceGroupHelper) DeleteAsync(ctx context.Context, subscriptionID string, resourceGroupName string) (err error) { - args := s.Called(ctx, subscriptionID, resourceGroupName) - return args.Error(0) -} diff --git a/aci/convert/container.go b/aci/convert/container.go deleted file mode 100644 index 179de8466..000000000 --- a/aci/convert/container.go +++ /dev/null @@ -1,94 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package convert - -import ( - "fmt" - "strings" - - "github.com/compose-spec/compose-go/types" - - "github.com/docker/compose-cli/api/containers" -) - -// ContainerToComposeProject convert container config to compose project -func ContainerToComposeProject(r containers.ContainerConfig) (types.Project, error) { - var ports []types.ServicePortConfig - for _, p := range r.Ports { - ports = append(ports, types.ServicePortConfig{ - Target: p.ContainerPort, - Published: p.HostPort, - Protocol: p.Protocol, - }) - } - - projectVolumes, serviceConfigVolumes, err := GetRunVolumes(r.Volumes) - if err != nil { - return types.Project{}, err - } - - retries := uint64(r.Healthcheck.Retries) - project := types.Project{ - Name: r.ID, - Services: []types.ServiceConfig{ - { - Name: r.ID, - Image: r.Image, - Command: r.Command, - Ports: ports, - Labels: r.Labels, - Volumes: serviceConfigVolumes, - DomainName: r.DomainName, - Environment: toComposeEnvs(r.Environment), - HealthCheck: &types.HealthCheckConfig{ - Test: r.Healthcheck.Test, - Timeout: &r.Healthcheck.Timeout, - Interval: &r.Healthcheck.Interval, - Retries: &retries, - StartPeriod: &r.Healthcheck.StartPeriod, - Disable: r.Healthcheck.Disable, - }, - Deploy: &types.DeployConfig{ - Resources: types.Resources{ - Reservations: &types.Resource{ - NanoCPUs: fmt.Sprintf("%f", r.CPULimit), - MemoryBytes: types.UnitBytes(r.MemLimit.Value()), - }, - }, - RestartPolicy: &types.RestartPolicy{ - Condition: r.RestartPolicyCondition, - }, - }, - }, - }, - Volumes: projectVolumes, - } - return project, nil -} - -func toComposeEnvs(opts []string) types.MappingWithEquals { - result := types.MappingWithEquals{} - for _, env := range opts { - tokens := strings.SplitN(env, "=", 2) - if len(tokens) > 1 { - result[tokens[0]] = &tokens[1] - } else { - result[env] = nil - } - } - return result -} diff --git a/aci/convert/container_test.go b/aci/convert/container_test.go deleted file mode 100644 index 49f01895d..000000000 --- a/aci/convert/container_test.go +++ /dev/null @@ -1,87 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package convert - -import ( - "testing" - - "github.com/Azure/go-autorest/autorest/to" - "github.com/compose-spec/compose-go/types" - "gotest.tools/v3/assert" - - "github.com/docker/compose-cli/api/containers" -) - -func TestConvertContainerEnvironment(t *testing.T) { - container := containers.ContainerConfig{ - ID: "container1", - Command: []string{"echo", "Hello!"}, - Environment: []string{"key1=value1", "key2", "key3=value3"}, - } - project, err := ContainerToComposeProject(container) - assert.NilError(t, err) - service1 := project.Services[0] - assert.Equal(t, service1.Name, container.ID) - assert.DeepEqual(t, []string(service1.Command), container.Command) - assert.DeepEqual(t, service1.Environment, types.MappingWithEquals{ - "key1": to.StringPtr("value1"), - "key2": nil, - "key3": to.StringPtr("value3"), - }) -} - -func TestConvertRestartPolicy(t *testing.T) { - container := containers.ContainerConfig{ - ID: "container1", - RestartPolicyCondition: "none", - } - project, err := ContainerToComposeProject(container) - assert.NilError(t, err) - service1 := project.Services[0] - assert.Equal(t, service1.Name, container.ID) - assert.Equal(t, service1.Deploy.RestartPolicy.Condition, "none") -} - -func TestConvertDomainName(t *testing.T) { - container := containers.ContainerConfig{ - ID: "container1", - DomainName: "myapp", - } - project, err := ContainerToComposeProject(container) - assert.NilError(t, err) - service1 := project.Services[0] - assert.Equal(t, service1.Name, container.ID) - assert.Equal(t, service1.DomainName, "myapp") -} - -func TestConvertEnvVariables(t *testing.T) { - container := containers.ContainerConfig{ - ID: "container1", - Environment: []string{ - "key=value", - "key2=value=with=equal", - }, - } - project, err := ContainerToComposeProject(container) - assert.NilError(t, err) - service1 := project.Services[0] - assert.Equal(t, service1.Name, container.ID) - assert.DeepEqual(t, service1.Environment, types.MappingWithEquals{ - "key": to.StringPtr("value"), - "key2": to.StringPtr("value=with=equal"), - }) -} diff --git a/aci/convert/convert.go b/aci/convert/convert.go deleted file mode 100644 index d92f6ebfe..000000000 --- a/aci/convert/convert.go +++ /dev/null @@ -1,456 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package convert - -import ( - "context" - "fmt" - "math" - "os" - "strconv" - "strings" - "time" - - "github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2019-12-01/containerinstance" - "github.com/Azure/go-autorest/autorest/to" - "github.com/compose-spec/compose-go/types" - "github.com/pkg/errors" - - "github.com/docker/compose-cli/aci/login" - "github.com/docker/compose-cli/api/containers" - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/utils/formatter" -) - -const ( - // StatusRunning name of the ACI running status - StatusRunning = "Running" - // ComposeDNSSidecarName name of the dns sidecar container - ComposeDNSSidecarName = "aci--dns--sidecar" - - dnsSidecarImage = "docker/aci-hostnames-sidecar:1.0" -) - -// ToContainerGroup converts a compose project into a ACI container group -func ToContainerGroup(ctx context.Context, aciContext store.AciContext, p types.Project, storageHelper login.StorageLogin) (containerinstance.ContainerGroup, error) { - project := projectAciHelper(p) - containerGroupName := strings.ToLower(project.Name) - volumesSlice, err := project.getAciFileVolumes(ctx, storageHelper) - if err != nil { - return containerinstance.ContainerGroup{}, err - } - secretVolumes, err := project.getAciSecretVolumes() - if err != nil { - return containerinstance.ContainerGroup{}, err - } - allVolumes := append(volumesSlice, secretVolumes...) - var volumes *[]containerinstance.Volume - if len(allVolumes) > 0 { - volumes = &allVolumes - } - - registryCreds, err := getRegistryCredentials(p, newCliRegistryConfLoader()) - if err != nil { - return containerinstance.ContainerGroup{}, err - } - - var ctnrs []containerinstance.Container - restartPolicy, err := project.getRestartPolicy() - if err != nil { - return containerinstance.ContainerGroup{}, err - } - groupDefinition := containerinstance.ContainerGroup{ - Name: &containerGroupName, - Location: &aciContext.Location, - ContainerGroupProperties: &containerinstance.ContainerGroupProperties{ - OsType: containerinstance.Linux, - Containers: &ctnrs, - Volumes: volumes, - ImageRegistryCredentials: ®istryCreds, - RestartPolicy: restartPolicy, - }, - } - - var groupPorts []containerinstance.Port - var dnsLabelName *string - for _, s := range project.Services { - service := serviceConfigAciHelper(s) - containerDefinition, err := service.getAciContainer() - if err != nil { - return containerinstance.ContainerGroup{}, err - } - if service.Labels != nil && len(service.Labels) > 0 { - return containerinstance.ContainerGroup{}, errors.New("ACI integration does not support labels in compose applications") - } - - containerPorts, serviceGroupPorts, serviceDomainName, err := convertPortsToAci(service) - if err != nil { - return groupDefinition, err - } - containerDefinition.ContainerProperties.Ports = &containerPorts - groupPorts = append(groupPorts, serviceGroupPorts...) - if serviceDomainName != nil { - if dnsLabelName != nil && *serviceDomainName != *dnsLabelName { - return containerinstance.ContainerGroup{}, fmt.Errorf("ACI integration does not support specifying different domain names on services in the same compose application") - } - dnsLabelName = serviceDomainName - } - - ctnrs = append(ctnrs, containerDefinition) - } - if len(groupPorts) > 0 { - groupDefinition.ContainerGroupProperties.IPAddress = &containerinstance.IPAddress{ - Type: containerinstance.Public, - Ports: &groupPorts, - DNSNameLabel: dnsLabelName, - } - } - if len(project.Services) > 1 { - dnsSideCar := getDNSSidecar(project.Services) - ctnrs = append(ctnrs, dnsSideCar) - } - groupDefinition.ContainerGroupProperties.Containers = &ctnrs - - return groupDefinition, nil -} - -func durationToSeconds(d *types.Duration) *int32 { - if d == nil || *d == 0 { - return nil - } - v := int32(time.Duration(*d).Seconds()) - return &v -} - -func getDNSSidecar(services types.Services) containerinstance.Container { - names := []string{"/hosts"} - for _, service := range services { - names = append(names, service.Name) - if service.ContainerName != "" { - names = append(names, service.ContainerName) - } - } - dnsSideCar := containerinstance.Container{ - Name: to.StringPtr(ComposeDNSSidecarName), - ContainerProperties: &containerinstance.ContainerProperties{ - Image: to.StringPtr(dnsSidecarImage), - Command: &names, - Resources: &containerinstance.ResourceRequirements{ - Requests: &containerinstance.ResourceRequests{ - MemoryInGB: to.Float64Ptr(0.1), - CPU: to.Float64Ptr(0.01), - }, - }, - }, - } - return dnsSideCar -} - -type projectAciHelper types.Project - -type serviceConfigAciHelper types.ServiceConfig - -func (s serviceConfigAciHelper) getAciContainer() (containerinstance.Container, error) { - aciServiceVolumes, err := s.getAciFileVolumeMounts() - if err != nil { - return containerinstance.Container{}, err - } - serviceSecretVolumes, err := s.getAciSecretsVolumeMounts() - if err != nil { - return containerinstance.Container{}, err - } - allVolumes := append(aciServiceVolumes, serviceSecretVolumes...) - var volumes *[]containerinstance.VolumeMount - if len(allVolumes) > 0 { - volumes = &allVolumes - } - - resource, err := s.getResourceRequestsLimits() - if err != nil { - return containerinstance.Container{}, err - } - - containerName := s.Name - if s.ContainerName != "" { - containerName = s.ContainerName - } - - return containerinstance.Container{ - Name: to.StringPtr(containerName), - ContainerProperties: &containerinstance.ContainerProperties{ - Image: to.StringPtr(s.Image), - Command: to.StringSlicePtr(s.Command), - EnvironmentVariables: getEnvVariables(s.Environment), - Resources: resource, - VolumeMounts: volumes, - LivenessProbe: s.getLivenessProbe(), - }, - }, nil -} - -func (s serviceConfigAciHelper) getResourceRequestsLimits() (*containerinstance.ResourceRequirements, error) { - memRequest := 1. // Default 1 Gb - var cpuRequest float64 = 1 - var err error - hasMemoryRequest := func() bool { - return s.Deploy != nil && s.Deploy.Resources.Reservations != nil && s.Deploy.Resources.Reservations.MemoryBytes != 0 - } - hasCPURequest := func() bool { - return s.Deploy != nil && s.Deploy.Resources.Reservations != nil && s.Deploy.Resources.Reservations.NanoCPUs != "" - } - if hasMemoryRequest() { - memRequest = BytesToGB(float64(s.Deploy.Resources.Reservations.MemoryBytes)) - } - - if hasCPURequest() { - cpuRequest, err = strconv.ParseFloat(s.Deploy.Resources.Reservations.NanoCPUs, 0) - if err != nil { - return nil, err - } - } - memLimit := memRequest - cpuLimit := cpuRequest - if s.Deploy != nil && s.Deploy.Resources.Limits != nil { - if s.Deploy.Resources.Limits.MemoryBytes != 0 { - memLimit = BytesToGB(float64(s.Deploy.Resources.Limits.MemoryBytes)) - if !hasMemoryRequest() { - memRequest = memLimit - } - } - if s.Deploy.Resources.Limits.NanoCPUs != "" { - cpuLimit, err = strconv.ParseFloat(s.Deploy.Resources.Limits.NanoCPUs, 0) - if err != nil { - return nil, err - } - if !hasCPURequest() { - cpuRequest = cpuLimit - } - } - } - resources := containerinstance.ResourceRequirements{ - Requests: &containerinstance.ResourceRequests{ - MemoryInGB: to.Float64Ptr(memRequest), - CPU: to.Float64Ptr(cpuRequest), - }, - Limits: &containerinstance.ResourceLimits{ - MemoryInGB: to.Float64Ptr(memLimit), - CPU: to.Float64Ptr(cpuLimit), - }, - } - return &resources, nil -} - -func (s serviceConfigAciHelper) getLivenessProbe() *containerinstance.ContainerProbe { - if s.HealthCheck != nil && !s.HealthCheck.Disable && len(s.HealthCheck.Test) > 0 { - testArray := s.HealthCheck.Test - switch s.HealthCheck.Test[0] { - case "NONE", "CMD", "CMD-SHELL": - testArray = s.HealthCheck.Test[1:] - } - if len(testArray) == 0 { - return nil - } - - var retries *int32 - if s.HealthCheck.Retries != nil { - retries = to.Int32Ptr(int32(*s.HealthCheck.Retries)) - } - probe := containerinstance.ContainerProbe{ - Exec: &containerinstance.ContainerExec{ - Command: to.StringSlicePtr(testArray), - }, - InitialDelaySeconds: durationToSeconds(s.HealthCheck.StartPeriod), - PeriodSeconds: durationToSeconds(s.HealthCheck.Interval), - TimeoutSeconds: durationToSeconds(s.HealthCheck.Timeout), - } - if retries != nil && *retries > 0 { - probe.FailureThreshold = retries - } - return &probe - } - return nil -} - -func getEnvVariables(composeEnv types.MappingWithEquals) *[]containerinstance.EnvironmentVariable { - result := []containerinstance.EnvironmentVariable{} - for key, value := range composeEnv { - var strValue string - if value == nil { - strValue = os.Getenv(key) - } else { - strValue = *value - } - result = append(result, containerinstance.EnvironmentVariable{ - Name: to.StringPtr(key), - Value: to.StringPtr(strValue), - }) - } - return &result -} - -// BytesToGB convert bytes To GB -func BytesToGB(b float64) float64 { - f := b / 1024 / 1024 / 1024 // from bytes to gigabytes - return math.Round(f*100) / 100 -} - -func gbToBytes(memInBytes float64) uint64 { - return uint64(memInBytes * 1024 * 1024 * 1024) -} - -// ContainerGroupToServiceStatus convert from an ACI container definition to service status -func ContainerGroupToServiceStatus(containerID string, group containerinstance.ContainerGroup, container containerinstance.Container, region string) api.ServiceStatus { - var replicas = 1 - if GetStatus(container, group) != StatusRunning { - replicas = 0 - } - return api.ServiceStatus{ - ID: containerID, - Name: *container.Name, - Ports: formatter.PortsToStrings(ToPorts(group.IPAddress, *container.Ports), FQDN(group, region)), - Replicas: replicas, - Desired: 1, - } -} - -// FQDN retrieve the fully qualified domain name for a ContainerGroup -func FQDN(group containerinstance.ContainerGroup, region string) string { - fqdn := "" - if group.IPAddress != nil && group.IPAddress.DNSNameLabel != nil && *group.IPAddress.DNSNameLabel != "" { - fqdn = *group.IPAddress.DNSNameLabel + "." + region + ".azurecontainer.io" - } - return fqdn -} - -// ContainerGroupToContainer composes a Container from an ACI container definition -func ContainerGroupToContainer(containerID string, cg containerinstance.ContainerGroup, cc containerinstance.Container, region string) containers.Container { - command := "" - if cc.Command != nil { - command = strings.Join(*cc.Command, " ") - } - - status := GetStatus(cc, cg) - platform := string(cg.OsType) - - var envVars map[string]string - if cc.EnvironmentVariables != nil && len(*cc.EnvironmentVariables) != 0 { - envVars = map[string]string{} - for _, envVar := range *cc.EnvironmentVariables { - envVars[*envVar.Name] = *envVar.Value - } - } - - hostConfig := ToHostConfig(cc, cg) - config := &containers.RuntimeConfig{ - FQDN: FQDN(cg, region), - Env: envVars, - } - - var healthcheck = containers.Healthcheck{ - Disable: true, - } - if cc.LivenessProbe != nil && - cc.LivenessProbe.Exec != nil && - cc.LivenessProbe.Exec.Command != nil { - if len(*cc.LivenessProbe.Exec.Command) > 0 { - healthcheck.Disable = false - healthcheck.Test = *cc.LivenessProbe.Exec.Command - if cc.LivenessProbe.PeriodSeconds != nil { - healthcheck.Interval = types.Duration(int64(*cc.LivenessProbe.PeriodSeconds) * int64(time.Second)) - } - if cc.LivenessProbe.FailureThreshold != nil { - healthcheck.Retries = int(*cc.LivenessProbe.FailureThreshold) - } - if cc.LivenessProbe.TimeoutSeconds != nil { - healthcheck.Timeout = types.Duration(int64(*cc.LivenessProbe.TimeoutSeconds) * int64(time.Second)) - } - if cc.LivenessProbe.InitialDelaySeconds != nil { - healthcheck.StartPeriod = types.Duration(int64(*cc.LivenessProbe.InitialDelaySeconds) * int64(time.Second)) - } - } - } - - c := containers.Container{ - ID: containerID, - Status: status, - Image: to.String(cc.Image), - Command: command, - CPUTime: 0, - MemoryUsage: 0, - PidsCurrent: 0, - PidsLimit: 0, - Ports: ToPorts(cg.IPAddress, *cc.Ports), - Platform: platform, - Config: config, - HostConfig: hostConfig, - Healthcheck: healthcheck, - } - - return c -} - -// ToHostConfig convert an ACI container to host config value -func ToHostConfig(cc containerinstance.Container, cg containerinstance.ContainerGroup) *containers.HostConfig { - memLimits := uint64(0) - memRequest := uint64(0) - cpuLimit := 0. - cpuReservation := 0. - if cc.Resources != nil { - if cc.Resources.Limits != nil { - if cc.Resources.Limits.MemoryInGB != nil { - memLimits = gbToBytes(*cc.Resources.Limits.MemoryInGB) - } - if cc.Resources.Limits.CPU != nil { - cpuLimit = *cc.Resources.Limits.CPU - } - } - if cc.Resources.Requests != nil { - if cc.Resources.Requests.MemoryInGB != nil { - memRequest = gbToBytes(*cc.Resources.Requests.MemoryInGB) - } - if cc.Resources.Requests.CPU != nil { - cpuReservation = *cc.Resources.Requests.CPU - } - } - } - hostConfig := &containers.HostConfig{ - CPULimit: cpuLimit, - CPUReservation: cpuReservation, - MemoryLimit: memLimits, - MemoryReservation: memRequest, - RestartPolicy: toContainerRestartPolicy(cg.RestartPolicy), - } - return hostConfig -} - -// GetStatus returns status for the specified container -func GetStatus(container containerinstance.Container, group containerinstance.ContainerGroup) string { - status := GetGroupStatus(group) - if container.InstanceView != nil && container.InstanceView.CurrentState != nil { - status = *container.InstanceView.CurrentState.State - } - return status -} - -// GetGroupStatus returns status for the container group -func GetGroupStatus(group containerinstance.ContainerGroup) string { - if group.InstanceView != nil && group.InstanceView.State != nil { - return "Node " + *group.InstanceView.State - } - return api.UNKNOWN -} diff --git a/aci/convert/convert_test.go b/aci/convert/convert_test.go deleted file mode 100644 index fd6cf028f..000000000 --- a/aci/convert/convert_test.go +++ /dev/null @@ -1,600 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package convert - -import ( - "context" - "os" - "testing" - "time" - - "github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2019-12-01/containerinstance" - "github.com/Azure/go-autorest/autorest/to" - "github.com/compose-spec/compose-go/types" - "gotest.tools/v3/assert" - is "gotest.tools/v3/assert/cmp" - - "github.com/docker/compose-cli/api/containers" - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/pkg/api" -) - -var ( - convertCtx = store.AciContext{ - SubscriptionID: "subID", - ResourceGroup: "rg", - Location: "eu", - } - mockStorageHelper = &mockStorageLogin{} -) - -func TestProjectName(t *testing.T) { - project := types.Project{ - Name: "TEST", - } - containerGroup, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper) - assert.NilError(t, err) - assert.Equal(t, *containerGroup.Name, "test") -} - -func TestContainerGroupToContainer(t *testing.T) { - myContainerGroup := containerinstance.ContainerGroup{ - ContainerGroupProperties: &containerinstance.ContainerGroupProperties{ - IPAddress: &containerinstance.IPAddress{ - Ports: &[]containerinstance.Port{{ - Port: to.Int32Ptr(80), - }}, - IP: to.StringPtr("42.42.42.42"), - DNSNameLabel: to.StringPtr("myapp"), - }, - OsType: "Linux", - }, - } - myContainer := containerinstance.Container{ - Name: to.StringPtr("myContainerID"), - ContainerProperties: &containerinstance.ContainerProperties{ - Image: to.StringPtr("sha256:666"), - Command: to.StringSlicePtr([]string{"mycommand"}), - Ports: &[]containerinstance.ContainerPort{{ - Port: to.Int32Ptr(80), - }}, - EnvironmentVariables: nil, - InstanceView: &containerinstance.ContainerPropertiesInstanceView{ - RestartCount: nil, - CurrentState: &containerinstance.ContainerState{ - State: to.StringPtr("Running"), - }, - }, - Resources: &containerinstance.ResourceRequirements{ - Limits: &containerinstance.ResourceLimits{ - CPU: to.Float64Ptr(3), - MemoryInGB: to.Float64Ptr(0.2), - }, - Requests: &containerinstance.ResourceRequests{ - CPU: to.Float64Ptr(2), - MemoryInGB: to.Float64Ptr(0.1), - }, - }, - LivenessProbe: &containerinstance.ContainerProbe{ - Exec: &containerinstance.ContainerExec{ - Command: to.StringSlicePtr([]string{ - "my", - "command", - "--option", - }), - }, - PeriodSeconds: to.Int32Ptr(10), - FailureThreshold: to.Int32Ptr(3), - InitialDelaySeconds: to.Int32Ptr(2), - TimeoutSeconds: to.Int32Ptr(1), - }, - }, - } - - var expectedContainer = containers.Container{ - ID: "myContainerID", - Status: "Running", - Image: "sha256:666", - Command: "mycommand", - Platform: "Linux", - Ports: []containers.Port{{ - HostPort: uint32(80), - ContainerPort: uint32(80), - Protocol: "tcp", - HostIP: "42.42.42.42", - }}, - Config: &containers.RuntimeConfig{ - FQDN: "myapp.eastus.azurecontainer.io", - }, - HostConfig: &containers.HostConfig{ - CPULimit: 3, - CPUReservation: 2, - MemoryLimit: gbToBytes(0.2), - MemoryReservation: gbToBytes(0.1), - RestartPolicy: "any", - }, - Healthcheck: containers.Healthcheck{ - Disable: false, - Test: []string{ - "my", - "command", - "--option", - }, - Interval: types.Duration(10 * time.Second), - Retries: 3, - StartPeriod: types.Duration(2 * time.Second), - Timeout: types.Duration(time.Second), - }, - } - - container := ContainerGroupToContainer("myContainerID", myContainerGroup, myContainer, "eastus") - assert.DeepEqual(t, container, expectedContainer) -} - -func TestHealthcheckTranslation(t *testing.T) { - test := []string{ - "my", - "command", - "--option", - } - interval := types.Duration(10 * time.Second) - retries := uint64(42) - startPeriod := types.Duration(2 * time.Second) - timeout := types.Duration(3 * time.Second) - project := types.Project{ - Services: []types.ServiceConfig{ - { - Name: "service1", - Image: "image1", - HealthCheck: &types.HealthCheckConfig{ - Test: test, - Timeout: &timeout, - Interval: &interval, - Retries: &retries, - StartPeriod: &startPeriod, - Disable: false, - }, - }, - }, - } - - testHealthcheckTestPrefixRemoval := func(test []string, shellPreffix ...string) { - project.Services[0].HealthCheck.Test = append(shellPreffix, test...) - group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper) - assert.NilError(t, err) - assert.DeepEqual(t, (*group.Containers)[0].LivenessProbe.Exec.Command, to.StringSlicePtr(test)) - assert.Equal(t, *(*group.Containers)[0].LivenessProbe.PeriodSeconds, int32(10)) - assert.Assert(t, (*group.Containers)[0].LivenessProbe.SuccessThreshold == nil) - assert.Equal(t, *(*group.Containers)[0].LivenessProbe.FailureThreshold, int32(42)) - assert.Equal(t, *(*group.Containers)[0].LivenessProbe.InitialDelaySeconds, int32(2)) - assert.Equal(t, *(*group.Containers)[0].LivenessProbe.TimeoutSeconds, int32(3)) - } - - testHealthcheckTestPrefixRemoval(test) - testHealthcheckTestPrefixRemoval(test, "NONE") - testHealthcheckTestPrefixRemoval(test, "CMD") - testHealthcheckTestPrefixRemoval(test, "CMD-SHELL") - - project.Services[0].HealthCheck.Disable = true - group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper) - assert.NilError(t, err) - assert.Assert(t, (*group.Containers)[0].LivenessProbe == nil) -} - -func TestHealthcheckTranslationZeroValues(t *testing.T) { - test := []string{ - "my", - "command", - "--option", - } - interval := types.Duration(0) - retries := uint64(0) - startPeriod := types.Duration(0) - timeout := types.Duration(0) - - project := types.Project{ - Services: []types.ServiceConfig{ - { - Name: "service1", - Image: "image1", - HealthCheck: &types.HealthCheckConfig{ - Test: test, - Timeout: &timeout, - Interval: &interval, - Retries: &retries, - StartPeriod: &startPeriod, - Disable: false, - }, - }, - }, - } - group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper) - assert.NilError(t, err) - assert.DeepEqual(t, (*group.Containers)[0].LivenessProbe.Exec.Command, to.StringSlicePtr(test)) - assert.Assert(t, (*group.Containers)[0].LivenessProbe.PeriodSeconds == nil) - assert.Assert(t, (*group.Containers)[0].LivenessProbe.SuccessThreshold == nil) - assert.Assert(t, (*group.Containers)[0].LivenessProbe.FailureThreshold == nil) - assert.Assert(t, (*group.Containers)[0].LivenessProbe.InitialDelaySeconds == nil) - assert.Assert(t, (*group.Containers)[0].LivenessProbe.TimeoutSeconds == nil) -} - -func TestContainerGroupToServiceStatus(t *testing.T) { - myContainerGroup := containerinstance.ContainerGroup{ - ContainerGroupProperties: &containerinstance.ContainerGroupProperties{ - IPAddress: &containerinstance.IPAddress{ - Ports: &[]containerinstance.Port{{ - Port: to.Int32Ptr(80), - }}, - IP: to.StringPtr("42.42.42.42"), - }, - }, - } - myContainer := containerinstance.Container{ - Name: to.StringPtr("myContainerID"), - ContainerProperties: &containerinstance.ContainerProperties{ - Ports: &[]containerinstance.ContainerPort{{ - Port: to.Int32Ptr(80), - }}, - InstanceView: &containerinstance.ContainerPropertiesInstanceView{ - RestartCount: nil, - CurrentState: &containerinstance.ContainerState{ - State: to.StringPtr("Running"), - }, - }, - }, - } - - var expectedService = api.ServiceStatus{ - ID: "myContainerID", - Name: "myContainerID", - Ports: []string{"42.42.42.42:80->80/tcp"}, - Replicas: 1, - Desired: 1, - } - - container := ContainerGroupToServiceStatus("myContainerID", myContainerGroup, myContainer, "eastus") - assert.DeepEqual(t, container, expectedService) -} - -func TestComposeContainerGroupToContainerWithDnsSideCarSide(t *testing.T) { - project := types.Project{ - Services: []types.ServiceConfig{ - { - Name: "service1", - Image: "image1", - }, - { - Name: "service2", - Image: "image2", - }, - }, - } - - group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper) - assert.NilError(t, err) - assert.Assert(t, is.Len(*group.Containers, 3)) - - assert.Equal(t, *(*group.Containers)[0].Name, "service1") - assert.Equal(t, *(*group.Containers)[1].Name, "service2") - assert.Equal(t, *(*group.Containers)[2].Name, ComposeDNSSidecarName) - - assert.DeepEqual(t, *(*group.Containers)[2].Command, []string{"/hosts", "service1", "service2"}) - - assert.Equal(t, *(*group.Containers)[0].Image, "image1") - assert.Equal(t, *(*group.Containers)[1].Image, "image2") - assert.Equal(t, *(*group.Containers)[2].Image, dnsSidecarImage) -} - -func TestComposeSingleContainerGroupToContainerNoDnsSideCarSide(t *testing.T) { - project := types.Project{ - Services: []types.ServiceConfig{ - { - Name: "service1", - Image: "image1", - }, - }, - } - - group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper) - assert.NilError(t, err) - - assert.Assert(t, is.Len(*group.Containers, 1)) - assert.Equal(t, *(*group.Containers)[0].Name, "service1") - assert.Equal(t, *(*group.Containers)[0].Image, "image1") -} - -func TestLabelsErrorMessage(t *testing.T) { - project := types.Project{ - Services: []types.ServiceConfig{ - { - Name: "service1", - Image: "image1", - Labels: map[string]string{ - "label1": "value1", - }, - }, - }, - } - - _, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper) - assert.Error(t, err, "ACI integration does not support labels in compose applications") -} - -func TestComposeContainerGroupToContainerWithDomainName(t *testing.T) { - project := types.Project{ - Services: []types.ServiceConfig{ - { - Name: "service1", - Image: "image1", - Ports: []types.ServicePortConfig{ - { - Published: 80, - Target: 80, - }, - }, - DomainName: "myApp", - }, - { - Name: "service2", - Image: "image2", - Ports: []types.ServicePortConfig{ - { - Published: 8080, - Target: 8080, - }, - }, - }, - }, - } - - group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper) - assert.NilError(t, err) - assert.Assert(t, is.Len(*group.Containers, 3)) - - groupPorts := *group.IPAddress.Ports - assert.Assert(t, is.Len(groupPorts, 2)) - assert.Equal(t, *groupPorts[0].Port, int32(80)) - assert.Equal(t, *groupPorts[1].Port, int32(8080)) - assert.Equal(t, *group.IPAddress.DNSNameLabel, "myApp") -} - -func TestComposeContainerGroupToContainerErrorWhenSeveralDomainNames(t *testing.T) { - project := types.Project{ - Services: []types.ServiceConfig{ - { - Name: "service1", - Image: "image1", - DomainName: "myApp", - }, - { - Name: "service2", - Image: "image2", - DomainName: "myApp2", - }, - }, - } - - _, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper) - assert.Error(t, err, "ACI integration does not support specifying different domain names on services in the same compose application") -} - -// ACI fails if group definition IPAddress has no ports -func TestComposeContainerGroupToContainerIgnoreDomainNameWithoutPorts(t *testing.T) { - project := types.Project{ - Services: []types.ServiceConfig{ - { - Name: "service1", - Image: "image1", - DomainName: "myApp", - }, - { - Name: "service2", - Image: "image2", - DomainName: "myApp", - }, - }, - } - - group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper) - assert.NilError(t, err) - assert.Assert(t, is.Len(*group.Containers, 3)) - assert.Assert(t, group.IPAddress == nil) -} - -var _0_1Gb = gbToBytes(0.1) - -func TestComposeContainerGroupToContainerResourceRequests(t *testing.T) { - project := types.Project{ - Services: []types.ServiceConfig{ - { - Name: "service1", - Image: "image1", - Deploy: &types.DeployConfig{ - Resources: types.Resources{ - Reservations: &types.Resource{ - NanoCPUs: "0.1", - MemoryBytes: types.UnitBytes(_0_1Gb), - }, - }, - }, - }, - }, - } - - group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper) - assert.NilError(t, err) - - request := *((*group.Containers)[0]).Resources.Requests - assert.Equal(t, *request.CPU, float64(0.1)) - assert.Equal(t, *request.MemoryInGB, float64(0.1)) - limits := *((*group.Containers)[0]).Resources.Limits - assert.Equal(t, *limits.CPU, float64(0.1)) - assert.Equal(t, *limits.MemoryInGB, float64(0.1)) -} - -func TestComposeContainerGroupToContainerResourceRequestsAndLimits(t *testing.T) { - project := types.Project{ - Services: []types.ServiceConfig{ - { - Name: "service1", - Image: "image1", - Deploy: &types.DeployConfig{ - Resources: types.Resources{ - Reservations: &types.Resource{ - NanoCPUs: "0.1", - MemoryBytes: types.UnitBytes(_0_1Gb), - }, - Limits: &types.Resource{ - NanoCPUs: "0.3", - MemoryBytes: types.UnitBytes(2 * _0_1Gb), - }, - }, - }, - }, - }, - } - - group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper) - assert.NilError(t, err) - - request := *((*group.Containers)[0]).Resources.Requests - assert.Equal(t, *request.CPU, float64(0.1)) - assert.Equal(t, *request.MemoryInGB, float64(0.1)) - limits := *((*group.Containers)[0]).Resources.Limits - assert.Equal(t, *limits.CPU, float64(0.3)) - assert.Equal(t, *limits.MemoryInGB, float64(0.2)) -} - -func TestComposeContainerGroupToContainerResourceLimitsOnly(t *testing.T) { - project := types.Project{ - Services: []types.ServiceConfig{ - { - Name: "service1", - Image: "image1", - Deploy: &types.DeployConfig{ - Resources: types.Resources{ - Limits: &types.Resource{ - NanoCPUs: "0.3", - MemoryBytes: types.UnitBytes(2 * _0_1Gb), - }, - }, - }, - }, - }, - } - - group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper) - assert.NilError(t, err) - - request := *((*group.Containers)[0]).Resources.Requests - assert.Equal(t, *request.CPU, float64(0.3)) - assert.Equal(t, *request.MemoryInGB, float64(0.2)) - limits := *((*group.Containers)[0]).Resources.Limits - assert.Equal(t, *limits.CPU, float64(0.3)) - assert.Equal(t, *limits.MemoryInGB, float64(0.2)) -} - -func TestComposeContainerGroupToContainerResourceRequestsDefaults(t *testing.T) { - project := types.Project{ - Services: []types.ServiceConfig{ - { - Name: "service1", - Image: "image1", - Deploy: &types.DeployConfig{ - Resources: types.Resources{ - Reservations: &types.Resource{ - NanoCPUs: "", - MemoryBytes: 0, - }, - }, - }, - }, - }, - } - - group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper) - assert.NilError(t, err) - - request := *((*group.Containers)[0]).Resources.Requests - assert.Equal(t, *request.CPU, float64(1)) - assert.Equal(t, *request.MemoryInGB, float64(1)) -} - -func TestComposeContainerGroupToContainerenvVar(t *testing.T) { - err := os.Setenv("key2", "value2") - assert.NilError(t, err) - project := types.Project{ - Services: []types.ServiceConfig{ - { - Name: "service1", - Image: "image1", - Environment: types.MappingWithEquals{ - "key1": to.StringPtr("value1"), - "key2": nil, - }, - }, - }, - } - - group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper) - assert.NilError(t, err) - - envVars := *((*group.Containers)[0]).EnvironmentVariables - assert.Assert(t, is.Len(envVars, 2)) - assert.Assert(t, is.Contains(envVars, containerinstance.EnvironmentVariable{Name: to.StringPtr("key1"), Value: to.StringPtr("value1")})) - assert.Assert(t, is.Contains(envVars, containerinstance.EnvironmentVariable{Name: to.StringPtr("key2"), Value: to.StringPtr("value2")})) -} - -func TestConvertContainerGroupStatus(t *testing.T) { - assert.Equal(t, "Running", GetStatus(container(to.StringPtr("Running")), group(to.StringPtr("Started")))) - assert.Equal(t, "Terminated", GetStatus(container(to.StringPtr("Terminated")), group(to.StringPtr("Stopped")))) - assert.Equal(t, "Node Stopped", GetStatus(container(nil), group(to.StringPtr("Stopped")))) - assert.Equal(t, "Node Started", GetStatus(container(nil), group(to.StringPtr("Started")))) - - assert.Equal(t, "Running", GetStatus(container(to.StringPtr("Running")), group(nil))) - assert.Equal(t, "Unknown", GetStatus(container(nil), group(nil))) -} - -func container(status *string) containerinstance.Container { - var state *containerinstance.ContainerState - if status != nil { - state = &containerinstance.ContainerState{ - State: status, - } - } - return containerinstance.Container{ - ContainerProperties: &containerinstance.ContainerProperties{ - InstanceView: &containerinstance.ContainerPropertiesInstanceView{ - CurrentState: state, - }, - }, - } -} - -func group(status *string) containerinstance.ContainerGroup { - var view *containerinstance.ContainerGroupPropertiesInstanceView - if status != nil { - view = &containerinstance.ContainerGroupPropertiesInstanceView{ - State: status, - } - } - return containerinstance.ContainerGroup{ - ContainerGroupProperties: &containerinstance.ContainerGroupProperties{ - InstanceView: view, - }, - } -} diff --git a/aci/convert/ports.go b/aci/convert/ports.go deleted file mode 100644 index 12e6ea049..000000000 --- a/aci/convert/ports.go +++ /dev/null @@ -1,94 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package convert - -import ( - "fmt" - "strings" - - "github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2019-12-01/containerinstance" - "github.com/Azure/go-autorest/autorest/to" - "github.com/pkg/errors" - - "github.com/docker/compose-cli/api/containers" -) - -func convertPortsToAci(service serviceConfigAciHelper) ([]containerinstance.ContainerPort, []containerinstance.Port, *string, error) { - var groupPorts []containerinstance.Port - var containerPorts []containerinstance.ContainerPort - for _, portConfig := range service.Ports { - if portConfig.Published != 0 && portConfig.Published != portConfig.Target { - msg := fmt.Sprintf("Port mapping is not supported with ACI, cannot map port %d to %d for container %s", - portConfig.Published, portConfig.Target, service.Name) - return nil, nil, nil, errors.New(msg) - } - portNumber := int32(portConfig.Target) - var groupProtocol containerinstance.ContainerGroupNetworkProtocol - var containerProtocol containerinstance.ContainerNetworkProtocol - switch portConfig.Protocol { - case "tcp", "": - groupProtocol = containerinstance.TCP - containerProtocol = containerinstance.ContainerNetworkProtocolTCP - case "udp": - groupProtocol = containerinstance.UDP - containerProtocol = containerinstance.ContainerNetworkProtocolUDP - default: - return nil, nil, nil, fmt.Errorf("unknown protocol %q in exposed port for service %q", portConfig.Protocol, service.Name) - } - containerPorts = append(containerPorts, containerinstance.ContainerPort{ - Port: to.Int32Ptr(portNumber), - Protocol: containerProtocol, - }) - groupPorts = append(groupPorts, containerinstance.Port{ - Port: to.Int32Ptr(portNumber), - Protocol: groupProtocol, - }) - } - var dnsLabelName *string - if service.DomainName != "" { - dnsLabelName = &service.DomainName - } - return containerPorts, groupPorts, dnsLabelName, nil -} - -// ToPorts converts Azure container ports to api ports -func ToPorts(ipAddr *containerinstance.IPAddress, ports []containerinstance.ContainerPort) []containers.Port { - var result []containers.Port - - for _, port := range ports { - if port.Port == nil { - continue - } - protocol := "tcp" - if port.Protocol != "" { - protocol = string(port.Protocol) - } - ip := "" - if ipAddr != nil && ipAddr.IP != nil { - ip = *ipAddr.IP - } - - result = append(result, containers.Port{ - HostPort: uint32(*port.Port), - ContainerPort: uint32(*port.Port), - HostIP: ip, - Protocol: strings.ToLower(protocol), - }) - } - - return result -} diff --git a/aci/convert/ports_test.go b/aci/convert/ports_test.go deleted file mode 100644 index 7452f7111..000000000 --- a/aci/convert/ports_test.go +++ /dev/null @@ -1,257 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package convert - -import ( - "context" - "testing" - - "github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2019-12-01/containerinstance" - "github.com/Azure/go-autorest/autorest/to" - "github.com/compose-spec/compose-go/types" - "gotest.tools/v3/assert" - is "gotest.tools/v3/assert/cmp" - - "github.com/docker/compose-cli/api/containers" -) - -func TestComposeContainerGroupToContainerMultiplePorts(t *testing.T) { - project := types.Project{ - Services: []types.ServiceConfig{ - { - Name: "service1", - Image: "image1", - Ports: []types.ServicePortConfig{ - { - Published: 80, - Target: 80, - }, - }, - }, - { - Name: "service2", - Image: "image2", - Ports: []types.ServicePortConfig{ - { - Published: 8080, - Target: 8080, - }, - }, - }, - }, - } - - group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper) - assert.NilError(t, err) - assert.Assert(t, is.Len(*group.Containers, 3)) - - container1 := (*group.Containers)[0] - assert.Equal(t, *container1.Name, "service1") - assert.Equal(t, *container1.Image, "image1") - assert.Equal(t, *(*container1.Ports)[0].Port, int32(80)) - - container2 := (*group.Containers)[1] - assert.Equal(t, *container2.Name, "service2") - assert.Equal(t, *container2.Image, "image2") - assert.Equal(t, *(*container2.Ports)[0].Port, int32(8080)) - - groupPorts := *group.IPAddress.Ports - assert.Assert(t, is.Len(groupPorts, 2)) - assert.Equal(t, *groupPorts[0].Port, int32(80)) - assert.Equal(t, *groupPorts[1].Port, int32(8080)) - assert.Assert(t, group.IPAddress.DNSNameLabel == nil) -} - -func TestPortConvert(t *testing.T) { - expectedPorts := []containers.Port{ - { - HostPort: 80, - ContainerPort: 80, - HostIP: "10.0.0.1", - Protocol: "tcp", - }, - } - testCases := []struct { - name string - ip *containerinstance.IPAddress - ports []containerinstance.ContainerPort - expected []containers.Port - }{ - { - name: "convert port", - ip: &containerinstance.IPAddress{ - IP: to.StringPtr("10.0.0.1"), - }, - ports: []containerinstance.ContainerPort{ - { - Protocol: "tcp", - Port: to.Int32Ptr(80), - }, - }, - expected: expectedPorts, - }, - { - name: "with nil ip", - ip: nil, - ports: []containerinstance.ContainerPort{ - { - Protocol: "tcp", - Port: to.Int32Ptr(80), - }, - }, - expected: []containers.Port{ - { - HostPort: 80, - ContainerPort: 80, - HostIP: "", - Protocol: "tcp", - }, - }, - }, - { - name: "with nil ip value", - ip: &containerinstance.IPAddress{ - IP: nil, - }, - ports: []containerinstance.ContainerPort{ - { - Protocol: "tcp", - Port: to.Int32Ptr(80), - }, - }, - expected: []containers.Port{ - { - HostPort: 80, - ContainerPort: 80, - HostIP: "", - Protocol: "tcp", - }, - }, - }, - { - name: "skip nil ports", - ip: nil, - ports: []containerinstance.ContainerPort{ - { - Protocol: "tcp", - Port: to.Int32Ptr(80), - }, - { - Protocol: "tcp", - Port: nil, - }, - }, - expected: []containers.Port{ - { - HostPort: 80, - ContainerPort: 80, - HostIP: "", - Protocol: "tcp", - }, - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - ports := ToPorts(testCase.ip, testCase.ports) - assert.DeepEqual(t, testCase.expected, ports) - }) - } -} - -func TestConvertTCPPortsToAci(t *testing.T) { - service := types.ServiceConfig{ - Name: "myService", - Ports: []types.ServicePortConfig{ - { - Protocol: "", - Target: 80, - Published: 80, - }, - { - Protocol: "tcp", - Target: 90, - Published: 90, - }, - }, - } - containerPorts, groupPports, _, err := convertPortsToAci(serviceConfigAciHelper(service)) - assert.NilError(t, err) - assert.DeepEqual(t, containerPorts, []containerinstance.ContainerPort{ - { - Port: to.Int32Ptr(80), - Protocol: containerinstance.ContainerNetworkProtocolTCP, - }, - { - Port: to.Int32Ptr(90), - Protocol: containerinstance.ContainerNetworkProtocolTCP, - }, - }) - assert.DeepEqual(t, groupPports, []containerinstance.Port{ - { - Port: to.Int32Ptr(80), - Protocol: containerinstance.TCP, - }, - { - Port: to.Int32Ptr(90), - Protocol: containerinstance.TCP, - }, - }) -} - -func TestConvertUDPPortsToAci(t *testing.T) { - service := types.ServiceConfig{ - Name: "myService", - Ports: []types.ServicePortConfig{ - { - Protocol: "udp", - Target: 80, - Published: 80, - }, - }, - } - containerPorts, groupPports, _, err := convertPortsToAci(serviceConfigAciHelper(service)) - assert.NilError(t, err) - assert.DeepEqual(t, containerPorts, []containerinstance.ContainerPort{ - { - Port: to.Int32Ptr(80), - Protocol: containerinstance.ContainerNetworkProtocolUDP, - }, - }) - assert.DeepEqual(t, groupPports, []containerinstance.Port{ - { - Port: to.Int32Ptr(80), - Protocol: containerinstance.UDP, - }, - }) -} - -func TestConvertErrorOnMappingPorts(t *testing.T) { - service := types.ServiceConfig{ - Name: "myService", - Ports: []types.ServicePortConfig{ - { - Protocol: "", - Target: 80, - Published: 90, - }, - }, - } - _, _, _, err := convertPortsToAci(serviceConfigAciHelper(service)) - assert.Error(t, err, "Port mapping is not supported with ACI, cannot map port 90 to 80 for container myService") -} diff --git a/aci/convert/registry_credentials.go b/aci/convert/registry_credentials.go deleted file mode 100644 index 2ffbfe61d..000000000 --- a/aci/convert/registry_credentials.go +++ /dev/null @@ -1,186 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package convert - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "os" - "os/exec" - "strings" - - "golang.org/x/oauth2" - - "github.com/Azure/azure-sdk-for-go/profiles/latest/containerinstance/mgmt/containerinstance" - "github.com/Azure/go-autorest/autorest/to" - compose "github.com/compose-spec/compose-go/types" - "github.com/docker/cli/cli/config" - "github.com/docker/cli/cli/config/configfile" - "github.com/docker/cli/cli/config/types" - "github.com/pkg/errors" - - "github.com/docker/compose-cli/aci/login" -) - -const ( - // Specific username from ACR docs : https://github.com/Azure/acr/blob/master/docs/AAD-OAuth.md#getting-credentials-programatically - tokenUsername = "00000000-0000-0000-0000-000000000000" - dockerHub = "index.docker.io" -) - -type registryHelper interface { - getAllRegistryCredentials() (map[string]types.AuthConfig, error) - autoLoginAcr(registry string, loginService login.AzureLoginService) error -} - -type cliRegistryHelper struct { - cfg *configfile.ConfigFile -} - -func (c cliRegistryHelper) getAllRegistryCredentials() (map[string]types.AuthConfig, error) { - return c.cfg.GetAllCredentials() -} - -func newCliRegistryConfLoader() cliRegistryHelper { - return cliRegistryHelper{ - cfg: config.LoadDefaultConfigFile(os.Stderr), - } -} - -func getRegistryCredentials(project compose.Project, helper registryHelper) ([]containerinstance.ImageRegistryCredential, error) { - loginService, err := login.NewAzureLoginService() - if err != nil { - return nil, err - } - - var cloudEnvironment *login.CloudEnvironment - if ce, err := loginService.GetCloudEnvironment(); err != nil { - cloudEnvironment = &ce - } - - usedRegistries, acrRegistries := getUsedRegistries(project, cloudEnvironment) - for _, registry := range acrRegistries { - err := helper.autoLoginAcr(registry, loginService) - if err != nil { - fmt.Printf("WARNING: %v\n", err) - fmt.Printf("Could not automatically login to %s from your Azure login. Assuming you already logged in to the ACR registry\n", registry) - } - } - - allCreds, err := helper.getAllRegistryCredentials() - if err != nil { - return nil, err - } - var registryCreds []containerinstance.ImageRegistryCredential - for name, oneCred := range allCreds { - parsedURL, err := url.Parse(name) - // Credentials can contain some garbage, we don't return the error here - // because we don't care about these garbage creds. - if err != nil { - continue - } - - hostname := parsedURL.Host - if hostname == "" { - hostname = parsedURL.Path - } - if _, ok := usedRegistries[hostname]; ok { - if oneCred.Password != "" { - aciCredential := containerinstance.ImageRegistryCredential{ - Server: to.StringPtr(hostname), - Password: to.StringPtr(oneCred.Password), - Username: to.StringPtr(oneCred.Username), - } - registryCreds = append(registryCreds, aciCredential) - } else if oneCred.IdentityToken != "" { - userName := tokenUsername - if oneCred.Username != "" { - userName = oneCred.Username - } - aciCredential := containerinstance.ImageRegistryCredential{ - Server: to.StringPtr(hostname), - Password: to.StringPtr(oneCred.IdentityToken), - Username: to.StringPtr(userName), - } - registryCreds = append(registryCreds, aciCredential) - } - } - } - return registryCreds, nil -} - -func getUsedRegistries(project compose.Project, ce *login.CloudEnvironment) (map[string]bool, []string) { - usedRegistries := map[string]bool{} - acrRegistries := []string{} - - for _, service := range project.Services { - imageName := service.Image - tokens := strings.Split(imageName, "/") - registry := tokens[0] - if len(tokens) == 1 { // ! image names can include "." ... - registry = dockerHub - } else if !strings.Contains(registry, ".") { - registry = dockerHub - } else if ce != nil { - if suffix, present := ce.Suffixes[login.AcrSuffixKey]; present && strings.HasSuffix(registry, suffix) { - acrRegistries = append(acrRegistries, registry) - } - } - usedRegistries[registry] = true - } - return usedRegistries, acrRegistries -} - -func (c cliRegistryHelper) autoLoginAcr(registry string, loginService login.AzureLoginService) error { - token, tenantID, err := loginService.GetValidToken() - if err != nil { - return err - } - - data := url.Values{ - "grant_type": {"access_token"}, - "service": {registry}, - "tenant": {tenantID}, - "access_token": {token.AccessToken}, - } - repoAuthURL := fmt.Sprintf("https://%s/oauth2/exchange", registry) - res, err := http.Post(repoAuthURL, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) - if err != nil { - return errors.Wrap(err, "could not query ACR token") - } - bits, err := ioutil.ReadAll(res.Body) - if err != nil { - return errors.Wrap(err, "could not read response body") - } - if res.StatusCode != 200 { - return errors.Errorf("could not obtain ACR token from Azure login, status : %s, response: %s", res.Status, string(bits)) - } - - newToken := oauth2.Token{} - if err := json.Unmarshal(bits, &newToken); err != nil { - return errors.Wrap(err, "could not read ACR token") - } - cmd := exec.Command("docker", "login", "-u", tokenUsername, "-p", newToken.RefreshToken, registry) - bytes, err := cmd.CombinedOutput() - if err != nil { - return errors.Wrap(err, fmt.Sprintf("could not 'docker login' to %s :\n%s\n", registry, string(bytes))) - } - return nil -} diff --git a/aci/convert/registry_credentials_test.go b/aci/convert/registry_credentials_test.go deleted file mode 100644 index 78ca7556a..000000000 --- a/aci/convert/registry_credentials_test.go +++ /dev/null @@ -1,262 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package convert - -import ( - "errors" - "strconv" - "testing" - - "github.com/Azure/azure-sdk-for-go/profiles/latest/containerinstance/mgmt/containerinstance" - "github.com/Azure/go-autorest/autorest/to" - "github.com/compose-spec/compose-go/types" - cliconfigtypes "github.com/docker/cli/cli/config/types" - "github.com/docker/compose-cli/aci/login" - "github.com/stretchr/testify/mock" - "gotest.tools/v3/assert" - is "gotest.tools/v3/assert/cmp" -) - -const getAllCredentials = "getAllRegistryCredentials" -const autoLoginAcr = "autoLoginAcr" - -func TestHubPrivateImage(t *testing.T) { - registryHelper := &MockRegistryHelper{} - registryHelper.On(getAllCredentials).Return(registry("https://index.docker.io", userPwdCreds("toto", "pwd")), nil) - - creds, err := getRegistryCredentials(composeServices("gtardif/privateimg"), registryHelper) - assert.NilError(t, err) - assert.DeepEqual(t, creds, []containerinstance.ImageRegistryCredential{ - { - Server: to.StringPtr(dockerHub), - Username: to.StringPtr("toto"), - Password: to.StringPtr("pwd"), - }, - }) -} - -func TestRegistryNameWithoutProtocol(t *testing.T) { - registryHelper := &MockRegistryHelper{} - registryHelper.On(getAllCredentials).Return(registry("index.docker.io", userPwdCreds("toto", "pwd")), nil) - - creds, err := getRegistryCredentials(composeServices("gtardif/privateimg"), registryHelper) - assert.NilError(t, err) - assert.DeepEqual(t, creds, []containerinstance.ImageRegistryCredential{ - { - Server: to.StringPtr(dockerHub), - Username: to.StringPtr("toto"), - Password: to.StringPtr("pwd"), - }, - }) -} - -func TestInvalidCredentials(t *testing.T) { - registryHelper := &MockRegistryHelper{} - registryHelper.On(getAllCredentials).Return(registry("18.195.159.6:444", userPwdCreds("toto", "pwd")), nil) - - creds, err := getRegistryCredentials(composeServices("gtardif/privateimg"), registryHelper) - assert.NilError(t, err) - assert.Equal(t, len(creds), 0) -} - -func TestImageWithDotInName(t *testing.T) { - registryHelper := &MockRegistryHelper{} - registryHelper.On(getAllCredentials).Return(registry("index.docker.io", userPwdCreds("toto", "pwd")), nil) - - creds, err := getRegistryCredentials(composeServices("my.image"), registryHelper) - assert.NilError(t, err) - assert.DeepEqual(t, creds, []containerinstance.ImageRegistryCredential{ - { - Server: to.StringPtr(dockerHub), - Username: to.StringPtr("toto"), - Password: to.StringPtr("pwd"), - }, - }) -} - -func TestAcrPrivateImage(t *testing.T) { - registryHelper := &MockRegistryHelper{} - registryHelper.On(getAllCredentials).Return(registry("https://mycontainerregistrygta.azurecr.io", tokenCreds("123456")), nil) - registryHelper.On(autoLoginAcr, "mycontainerregistrygta.azurecr.io").Return(nil) - - creds, err := getRegistryCredentials(composeServices("mycontainerregistrygta.azurecr.io/privateimg"), registryHelper) - assert.NilError(t, err) - assert.DeepEqual(t, creds, []containerinstance.ImageRegistryCredential{ - { - Server: to.StringPtr("mycontainerregistrygta.azurecr.io"), - Username: to.StringPtr(tokenUsername), - Password: to.StringPtr("123456"), - }, - }) -} - -func TestAcrPrivateImageLinux(t *testing.T) { - registryHelper := &MockRegistryHelper{} - token := tokenCreds("123456") - token.Username = tokenUsername - registryHelper.On(getAllCredentials).Return(registry("https://mycontainerregistrygta.azurecr.io", token), nil) - registryHelper.On(autoLoginAcr, "mycontainerregistrygta.azurecr.io").Return(nil) - - creds, err := getRegistryCredentials(composeServices("mycontainerregistrygta.azurecr.io/privateimg"), registryHelper) - assert.NilError(t, err) - assert.DeepEqual(t, creds, []containerinstance.ImageRegistryCredential{ - { - Server: to.StringPtr("mycontainerregistrygta.azurecr.io"), - Username: to.StringPtr(tokenUsername), - Password: to.StringPtr("123456"), - }, - }) -} - -func TestNoMoreRegistriesThanImages(t *testing.T) { - registryHelper := &MockRegistryHelper{} - configs := map[string]cliconfigtypes.AuthConfig{ - "https://mycontainerregistrygta.azurecr.io": tokenCreds("123456"), - "https://index.docker.io": userPwdCreds("toto", "pwd"), - } - registryHelper.On(getAllCredentials).Return(configs, nil) - registryHelper.On(autoLoginAcr, "mycontainerregistrygta.azurecr.io").Return(nil) - - creds, err := getRegistryCredentials(composeServices("mycontainerregistrygta.azurecr.io/privateimg"), registryHelper) - assert.NilError(t, err) - assert.DeepEqual(t, creds, []containerinstance.ImageRegistryCredential{ - { - Server: to.StringPtr("mycontainerregistrygta.azurecr.io"), - Username: to.StringPtr(tokenUsername), - Password: to.StringPtr("123456"), - }, - }) - - creds, err = getRegistryCredentials(composeServices("someuser/privateimg"), registryHelper) - assert.NilError(t, err) - assert.DeepEqual(t, creds, []containerinstance.ImageRegistryCredential{ - { - Server: to.StringPtr(dockerHub), - Username: to.StringPtr("toto"), - Password: to.StringPtr("pwd"), - }, - }) -} - -func TestHubAndSeveralACRRegistries(t *testing.T) { - registryHelper := &MockRegistryHelper{} - configs := map[string]cliconfigtypes.AuthConfig{ - "https://mycontainerregistry1.azurecr.io": tokenCreds("123456"), - "https://mycontainerregistry2.azurecr.io": tokenCreds("456789"), - "https://mycontainerregistry3.azurecr.io": tokenCreds("123456789"), - "https://index.docker.io": userPwdCreds("toto", "pwd"), - "https://other.registry.io": userPwdCreds("user", "password"), - } - registryHelper.On(getAllCredentials).Return(configs, nil) - registryHelper.On(autoLoginAcr, "mycontainerregistry1.azurecr.io").Return(nil) - registryHelper.On(autoLoginAcr, "mycontainerregistry2.azurecr.io").Return(nil) - - creds, err := getRegistryCredentials(composeServices("mycontainerregistry1.azurecr.io/privateimg", "someuser/privateImg2", "mycontainerregistry2.azurecr.io/privateimg"), registryHelper) - assert.NilError(t, err) - - assert.Assert(t, is.Contains(creds, containerinstance.ImageRegistryCredential{ - Server: to.StringPtr("mycontainerregistry1.azurecr.io"), - Username: to.StringPtr(tokenUsername), - Password: to.StringPtr("123456"), - })) - assert.Assert(t, is.Contains(creds, containerinstance.ImageRegistryCredential{ - Server: to.StringPtr("mycontainerregistry2.azurecr.io"), - Username: to.StringPtr(tokenUsername), - Password: to.StringPtr("456789"), - })) - assert.Assert(t, is.Contains(creds, containerinstance.ImageRegistryCredential{ - Server: to.StringPtr(dockerHub), - Username: to.StringPtr("toto"), - Password: to.StringPtr("pwd"), - })) -} - -func TestIgnoreACRRegistryFailedAutoLogin(t *testing.T) { - registryHelper := &MockRegistryHelper{} - configs := map[string]cliconfigtypes.AuthConfig{ - "https://mycontainerregistry1.azurecr.io": tokenCreds("123456"), - "https://mycontainerregistry3.azurecr.io": tokenCreds("123456789"), - "https://index.docker.io": userPwdCreds("toto", "pwd"), - "https://other.registry.io": userPwdCreds("user", "password"), - } - registryHelper.On(getAllCredentials).Return(configs, nil) - registryHelper.On(autoLoginAcr, "mycontainerregistry1.azurecr.io").Return(nil) - registryHelper.On(autoLoginAcr, "mycontainerregistry2.azurecr.io").Return(errors.New("could not login")) - - creds, err := getRegistryCredentials(composeServices("mycontainerregistry1.azurecr.io/privateimg", "someuser/privateImg2", "mycontainerregistry2.azurecr.io/privateimg"), registryHelper) - assert.NilError(t, err) - assert.Equal(t, len(creds), 2) - - assert.Assert(t, is.Contains(creds, containerinstance.ImageRegistryCredential{ - Server: to.StringPtr("mycontainerregistry1.azurecr.io"), - Username: to.StringPtr(tokenUsername), - Password: to.StringPtr("123456"), - })) - - assert.Assert(t, is.Contains(creds, containerinstance.ImageRegistryCredential{ - Server: to.StringPtr(dockerHub), - Username: to.StringPtr("toto"), - Password: to.StringPtr("pwd"), - })) -} - -func composeServices(images ...string) types.Project { - var services []types.ServiceConfig - for index, name := range images { - service := types.ServiceConfig{ - Name: "service" + strconv.Itoa(index), - Image: name, - } - services = append(services, service) - } - return types.Project{ - Services: services, - } -} - -func registry(host string, configregistryData cliconfigtypes.AuthConfig) map[string]cliconfigtypes.AuthConfig { - return map[string]cliconfigtypes.AuthConfig{ - host: configregistryData, - } -} - -func userPwdCreds(user string, password string) cliconfigtypes.AuthConfig { - return cliconfigtypes.AuthConfig{ - Username: user, - Password: password, - } -} - -func tokenCreds(token string) cliconfigtypes.AuthConfig { - return cliconfigtypes.AuthConfig{ - IdentityToken: token, - } -} - -type MockRegistryHelper struct { - mock.Mock -} - -func (s *MockRegistryHelper) getAllRegistryCredentials() (map[string]cliconfigtypes.AuthConfig, error) { - args := s.Called() - return args.Get(0).(map[string]cliconfigtypes.AuthConfig), args.Error(1) -} - -func (s *MockRegistryHelper) autoLoginAcr(registry string, loginService login.AzureLoginService) error { - args := s.Called(registry, loginService) - return args.Error(0) -} diff --git a/aci/convert/restartpolicy.go b/aci/convert/restartpolicy.go deleted file mode 100644 index 83cb03a2c..000000000 --- a/aci/convert/restartpolicy.go +++ /dev/null @@ -1,72 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package convert - -import ( - "github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2019-12-01/containerinstance" - "github.com/pkg/errors" - - "github.com/docker/compose-cli/api/containers" -) - -func (p projectAciHelper) getRestartPolicy() (containerinstance.ContainerGroupRestartPolicy, error) { - var restartPolicyCondition containerinstance.ContainerGroupRestartPolicy - if len(p.Services) >= 1 { - alreadySpecified := false - restartPolicyCondition = containerinstance.Always - for _, service := range p.Services { - if service.Deploy != nil && - service.Deploy.RestartPolicy != nil { - if !alreadySpecified { - alreadySpecified = true - restartPolicyCondition = toAciRestartPolicy(service.Deploy.RestartPolicy.Condition) - } - if alreadySpecified && restartPolicyCondition != toAciRestartPolicy(service.Deploy.RestartPolicy.Condition) { - return "", errors.New("ACI integration does not support specifying different restart policies on services in the same compose application") - } - - } - } - } - return restartPolicyCondition, nil -} - -func toAciRestartPolicy(restartPolicy string) containerinstance.ContainerGroupRestartPolicy { - switch restartPolicy { - case containers.RestartPolicyNone: - return containerinstance.Never - case containers.RestartPolicyAny: - return containerinstance.Always - case containers.RestartPolicyOnFailure: - return containerinstance.OnFailure - default: - return containerinstance.Always - } -} - -func toContainerRestartPolicy(aciRestartPolicy containerinstance.ContainerGroupRestartPolicy) string { - switch aciRestartPolicy { - case containerinstance.Never: - return containers.RestartPolicyNone - case containerinstance.Always: - return containers.RestartPolicyAny - case containerinstance.OnFailure: - return containers.RestartPolicyOnFailure - default: - return containers.RestartPolicyAny - } -} diff --git a/aci/convert/restartpolicy_test.go b/aci/convert/restartpolicy_test.go deleted file mode 100644 index 7dcf88643..000000000 --- a/aci/convert/restartpolicy_test.go +++ /dev/null @@ -1,144 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package convert - -import ( - "context" - "testing" - - "github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2019-12-01/containerinstance" - "github.com/compose-spec/compose-go/types" - "gotest.tools/v3/assert" - is "gotest.tools/v3/assert/cmp" -) - -func TestComposeSingleContainerRestartPolicy(t *testing.T) { - project := types.Project{ - Services: []types.ServiceConfig{ - { - Name: "service1", - Image: "image1", - Deploy: &types.DeployConfig{ - RestartPolicy: &types.RestartPolicy{ - Condition: "on-failure", - }, - }, - }, - }, - } - - group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper) - assert.NilError(t, err) - - assert.Assert(t, is.Len(*group.Containers, 1)) - assert.Equal(t, *(*group.Containers)[0].Name, "service1") - assert.Equal(t, group.RestartPolicy, containerinstance.OnFailure) -} - -func TestComposeMultiContainerRestartPolicy(t *testing.T) { - project := types.Project{ - Services: []types.ServiceConfig{ - { - Name: "service1", - Image: "image1", - Deploy: &types.DeployConfig{ - RestartPolicy: &types.RestartPolicy{ - Condition: "on-failure", - }, - }, - }, - { - Name: "service2", - Image: "image2", - Deploy: &types.DeployConfig{ - RestartPolicy: &types.RestartPolicy{ - Condition: "on-failure", - }, - }, - }, - }, - } - - group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper) - assert.NilError(t, err) - - assert.Assert(t, is.Len(*group.Containers, 3)) - assert.Equal(t, *(*group.Containers)[0].Name, "service1") - assert.Equal(t, group.RestartPolicy, containerinstance.OnFailure) - assert.Equal(t, *(*group.Containers)[1].Name, "service2") - assert.Equal(t, group.RestartPolicy, containerinstance.OnFailure) -} - -func TestComposeInconsistentMultiContainerRestartPolicy(t *testing.T) { - project := types.Project{ - Services: []types.ServiceConfig{ - { - Name: "service1", - Image: "image1", - Deploy: &types.DeployConfig{ - RestartPolicy: &types.RestartPolicy{ - Condition: "any", - }, - }, - }, - { - Name: "service2", - Image: "image2", - Deploy: &types.DeployConfig{ - RestartPolicy: &types.RestartPolicy{ - Condition: "on-failure", - }, - }, - }, - }, - } - - _, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper) - assert.Error(t, err, "ACI integration does not support specifying different restart policies on services in the same compose application") -} - -func TestComposeSingleContainerGroupToContainerDefaultRestartPolicy(t *testing.T) { - project := types.Project{ - Services: []types.ServiceConfig{ - { - Name: "service1", - Image: "image1", - }, - }, - } - - group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper) - assert.NilError(t, err) - - assert.Assert(t, is.Len(*group.Containers, 1)) - assert.Equal(t, *(*group.Containers)[0].Name, "service1") - assert.Equal(t, group.RestartPolicy, containerinstance.Always) -} - -func TestConvertToAciRestartPolicyCondition(t *testing.T) { - assert.Equal(t, toAciRestartPolicy("none"), containerinstance.Never) - assert.Equal(t, toAciRestartPolicy("always"), containerinstance.Always) - assert.Equal(t, toAciRestartPolicy("on-failure"), containerinstance.OnFailure) - assert.Equal(t, toAciRestartPolicy("on-failure:5"), containerinstance.Always) -} - -func TestConvertToDockerRestartPolicyCondition(t *testing.T) { - assert.Equal(t, toContainerRestartPolicy(containerinstance.Never), "none") - assert.Equal(t, toContainerRestartPolicy(containerinstance.Always), "any") - assert.Equal(t, toContainerRestartPolicy(containerinstance.OnFailure), "on-failure") - assert.Equal(t, toContainerRestartPolicy(""), "any") -} diff --git a/aci/convert/secrets.go b/aci/convert/secrets.go deleted file mode 100644 index 343077b65..000000000 --- a/aci/convert/secrets.go +++ /dev/null @@ -1,144 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package convert - -import ( - "encoding/base64" - "fmt" - "io/ioutil" - "path" - "strings" - - "github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2019-12-01/containerinstance" - "github.com/Azure/go-autorest/autorest/to" - "github.com/pkg/errors" -) - -const ( - defaultSecretsPath = "/run/secrets" - serviceSecretAbsPathPrefix = "aci-service-secret-path-" -) - -func getServiceSecretKey(serviceName, targetDir string) string { - return fmt.Sprintf("%s-%s--%s", - serviceSecretAbsPathPrefix, serviceName, strings.ReplaceAll(targetDir, "/", "-")) -} - -func (p projectAciHelper) getAciSecretVolumes() ([]containerinstance.Volume, error) { - var secretVolumes []containerinstance.Volume - for _, svc := range p.Services { - squashedTargetVolumes := make(map[string]containerinstance.Volume) - for _, scr := range svc.Secrets { - data, err := ioutil.ReadFile(p.Secrets[scr.Source].File) - if err != nil { - return secretVolumes, err - } - if len(data) == 0 { - continue - } - dataStr := base64.StdEncoding.EncodeToString(data) - if scr.Target == "" { - scr.Target = scr.Source - } - - if !path.IsAbs(scr.Target) && strings.ContainsAny(scr.Target, "\\/") { - return []containerinstance.Volume{}, - errors.Errorf("in service %q, secret with source %q cannot have a relative path as target. "+ - "Only absolute paths are allowed. Found %q", - svc.Name, scr.Source, scr.Target) - } - - if !path.IsAbs(scr.Target) { - scr.Target = path.Join(defaultSecretsPath, scr.Target) - } - - targetDir := path.Dir(scr.Target) - targetDirKey := getServiceSecretKey(svc.Name, targetDir) - if _, ok := squashedTargetVolumes[targetDir]; !ok { - squashedTargetVolumes[targetDir] = containerinstance.Volume{ - Name: to.StringPtr(targetDirKey), - Secret: make(map[string]*string), - } - } - - squashedTargetVolumes[targetDir].Secret[path.Base(scr.Target)] = &dataStr - } - for _, v := range squashedTargetVolumes { - secretVolumes = append(secretVolumes, v) - } - } - - return secretVolumes, nil -} - -func (s serviceConfigAciHelper) getAciSecretsVolumeMounts() ([]containerinstance.VolumeMount, error) { - vms := []containerinstance.VolumeMount{} - presenceSet := make(map[string]bool) - for _, scr := range s.Secrets { - if scr.Target == "" { - scr.Target = scr.Source - } - if !path.IsAbs(scr.Target) { - scr.Target = path.Join(defaultSecretsPath, scr.Target) - } - - presenceKey := path.Dir(scr.Target) - if !presenceSet[presenceKey] { - vms = append(vms, containerinstance.VolumeMount{ - Name: to.StringPtr(getServiceSecretKey(s.Name, path.Dir(scr.Target))), - MountPath: to.StringPtr(path.Dir(scr.Target)), - ReadOnly: to.BoolPtr(true), - }) - presenceSet[presenceKey] = true - } - } - err := validateMountPathCollisions(vms) - if err != nil { - return []containerinstance.VolumeMount{}, err - } - return vms, nil -} - -func validateMountPathCollisions(vms []containerinstance.VolumeMount) error { - for i, vm1 := range vms { - for j, vm2 := range vms { - if i == j { - continue - } - var ( - biggerVMPath = strings.Split(*vm1.MountPath, "/") - smallerVMPath = strings.Split(*vm2.MountPath, "/") - ) - if len(smallerVMPath) > len(biggerVMPath) { - tmp := biggerVMPath - biggerVMPath = smallerVMPath - smallerVMPath = tmp - } - isPrefixed := true - for i := 0; i < len(smallerVMPath); i++ { - if smallerVMPath[i] != biggerVMPath[i] { - isPrefixed = false - break - } - } - if isPrefixed { - return errors.Errorf("mount paths %q and %q collide. A volume mount cannot include another one.", *vm1.MountPath, *vm2.MountPath) - } - } - } - return nil -} diff --git a/aci/convert/secrets_test.go b/aci/convert/secrets_test.go deleted file mode 100644 index 53156182e..000000000 --- a/aci/convert/secrets_test.go +++ /dev/null @@ -1,179 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package convert - -import ( - "fmt" - "io/ioutil" - "os" - "path" - "testing" - - "github.com/compose-spec/compose-go/types" - "gotest.tools/v3/assert" -) - -func TestConvertSecrets(t *testing.T) { - serviceName := "testservice" - secretName := "testsecret" - absBasePath := "/home/user" - tmpFile, err := ioutil.TempFile(os.TempDir(), "TestConvertProjectSecrets-") - assert.NilError(t, err) - _, err = tmpFile.Write([]byte("test content")) - assert.NilError(t, err) - t.Cleanup(func() { - _ = os.Remove(tmpFile.Name()) - }) - - t.Run("mix default and absolute", func(t *testing.T) { - pSquashedDefaultAndAbs := projectAciHelper{ - Services: []types.ServiceConfig{ - { - Name: serviceName, - Secrets: []types.ServiceSecretConfig{ - { - Source: secretName, - Target: "some_target1", - }, - { - Source: secretName, - }, - { - Source: secretName, - Target: path.Join(defaultSecretsPath, "some_target2"), - }, - { - Source: secretName, - Target: path.Join(absBasePath, "some_target3"), - }, - { - Source: secretName, - Target: path.Join(absBasePath, "some_target4"), - }, - }, - }, - }, - Secrets: map[string]types.SecretConfig{ - secretName: { - File: tmpFile.Name(), - }, - }, - } - volumes, err := pSquashedDefaultAndAbs.getAciSecretVolumes() - assert.NilError(t, err) - assert.Equal(t, len(volumes), 2) - - defaultVolumeName := getServiceSecretKey(serviceName, defaultSecretsPath) - homeVolumeName := getServiceSecretKey(serviceName, absBasePath) - // random order since this was created from a map... - for _, vol := range volumes { - switch *vol.Name { - case defaultVolumeName: - assert.Equal(t, len(vol.Secret), 3) - case homeVolumeName: - assert.Equal(t, len(vol.Secret), 2) - default: - assert.Assert(t, false, "unexpected volume name: "+*vol.Name) - } - } - - s := serviceConfigAciHelper(pSquashedDefaultAndAbs.Services[0]) - vms, err := s.getAciSecretsVolumeMounts() - assert.NilError(t, err) - assert.Equal(t, len(vms), 2) - - assert.Equal(t, *vms[0].Name, defaultVolumeName) - assert.Equal(t, *vms[0].MountPath, defaultSecretsPath) - - assert.Equal(t, *vms[1].Name, homeVolumeName) - assert.Equal(t, *vms[1].MountPath, absBasePath) - }) - - t.Run("convert invalid target", func(t *testing.T) { - targetName := "some/invalid/relative/path/target" - pInvalidRelativePathTarget := projectAciHelper{ - Services: []types.ServiceConfig{ - { - Name: serviceName, - Secrets: []types.ServiceSecretConfig{ - { - Source: secretName, - Target: targetName, - }, - }, - }, - }, - Secrets: map[string]types.SecretConfig{ - secretName: { - File: tmpFile.Name(), - }, - }, - } - _, err := pInvalidRelativePathTarget.getAciSecretVolumes() - assert.Equal(t, err.Error(), - fmt.Sprintf(`in service %q, secret with source %q cannot have a relative path as target. Only absolute paths are allowed. Found %q`, - serviceName, secretName, targetName)) - }) - - t.Run("convert colliding default targets", func(t *testing.T) { - targetName1 := path.Join(defaultSecretsPath, "target1") - targetName2 := path.Join(defaultSecretsPath, "sub/folder/target2") - - service := serviceConfigAciHelper{ - Name: serviceName, - Secrets: []types.ServiceSecretConfig{ - { - Source: secretName, - Target: targetName1, - }, - { - Source: secretName, - Target: targetName2, - }, - }, - } - - _, err := service.getAciSecretsVolumeMounts() - assert.Equal(t, err.Error(), - fmt.Sprintf(`mount paths %q and %q collide. A volume mount cannot include another one.`, - path.Dir(targetName1), path.Dir(targetName2))) - }) - - t.Run("convert colliding absolute targets", func(t *testing.T) { - targetName1 := path.Join(absBasePath, "target1") - targetName2 := path.Join(absBasePath, "sub/folder/target2") - - service := serviceConfigAciHelper{ - Name: serviceName, - Secrets: []types.ServiceSecretConfig{ - { - Source: secretName, - Target: targetName1, - }, - { - Source: secretName, - Target: targetName2, - }, - }, - } - - _, err := service.getAciSecretsVolumeMounts() - assert.Equal(t, err.Error(), - fmt.Sprintf(`mount paths %q and %q collide. A volume mount cannot include another one.`, - path.Dir(targetName1), path.Dir(targetName2))) - }) -} diff --git a/aci/convert/volume.go b/aci/convert/volume.go deleted file mode 100644 index 79b6fcb36..000000000 --- a/aci/convert/volume.go +++ /dev/null @@ -1,177 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package convert - -import ( - "context" - "fmt" - "strconv" - "strings" - - "github.com/docker/compose-cli/aci/login" - "github.com/docker/compose-cli/pkg/api" - - "github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2019-12-01/containerinstance" - "github.com/Azure/go-autorest/autorest/to" - "github.com/compose-spec/compose-go/types" - "github.com/pkg/errors" -) - -const ( - // AzureFileDriverName driver name for azure file share - AzureFileDriverName = "azure_file" - // VolumeDriveroptsShareNameKey driver opt for fileshare name - VolumeDriveroptsShareNameKey = "share_name" - // VolumeDriveroptsAccountNameKey driver opt for storage account - VolumeDriveroptsAccountNameKey = "storage_account_name" - volumeReadOnly = "read_only" -) - -func (p projectAciHelper) getAciFileVolumes(ctx context.Context, helper login.StorageLogin) ([]containerinstance.Volume, error) { - var azureFileVolumesSlice []containerinstance.Volume - for name, v := range p.Volumes { - if v.Driver == AzureFileDriverName { - shareName, ok := v.DriverOpts[VolumeDriveroptsShareNameKey] - if !ok { - return nil, fmt.Errorf("cannot retrieve fileshare name for Azurefile") - } - accountName, ok := v.DriverOpts[VolumeDriveroptsAccountNameKey] - if !ok { - return nil, fmt.Errorf("cannot retrieve account name for Azurefile") - } - readOnly, ok := v.DriverOpts[volumeReadOnly] - if !ok { - readOnly = "false" - } - ro, err := strconv.ParseBool(readOnly) - if err != nil { - return nil, fmt.Errorf("invalid mode %q for volume", readOnly) - } - accountKey, err := helper.GetAzureStorageAccountKey(ctx, accountName) - if err != nil { - return nil, err - } - aciVolume := containerinstance.Volume{ - Name: to.StringPtr(name), - AzureFile: &containerinstance.AzureFileVolume{ - ShareName: to.StringPtr(shareName), - StorageAccountName: to.StringPtr(accountName), - StorageAccountKey: to.StringPtr(accountKey), - ReadOnly: &ro, - }, - } - azureFileVolumesSlice = append(azureFileVolumesSlice, aciVolume) - } - } - return azureFileVolumesSlice, nil -} - -func (s serviceConfigAciHelper) getAciFileVolumeMounts() ([]containerinstance.VolumeMount, error) { - var aciServiceVolumes []containerinstance.VolumeMount - for _, sv := range s.Volumes { - if sv.Type == string(types.VolumeTypeBind) { - return []containerinstance.VolumeMount{}, fmt.Errorf("host path (%q) not allowed as volume source, you need to reference an Azure File Share defined in the 'volumes' section", sv.Source) - } - aciServiceVolumes = append(aciServiceVolumes, containerinstance.VolumeMount{ - Name: to.StringPtr(sv.Source), - MountPath: to.StringPtr(sv.Target), - }) - } - return aciServiceVolumes, nil -} - -// GetRunVolumes return volume configurations for a project and a single service -// this is meant to be used as a compose project of a single service -func GetRunVolumes(volumes []string) (map[string]types.VolumeConfig, []types.ServiceVolumeConfig, error) { - var serviceConfigVolumes []types.ServiceVolumeConfig - projectVolumes := make(map[string]types.VolumeConfig, len(volumes)) - for i, v := range volumes { - var vi volumeInput - err := vi.parse(fmt.Sprintf("volume-%d", i), v) - if err != nil { - return nil, nil, err - } - readOnly := strconv.FormatBool(vi.readonly) - projectVolumes[vi.name] = types.VolumeConfig{ - Name: vi.name, - Driver: AzureFileDriverName, - DriverOpts: map[string]string{ - VolumeDriveroptsAccountNameKey: vi.storageAccount, - VolumeDriveroptsShareNameKey: vi.fileshare, - volumeReadOnly: readOnly, - }, - } - sv := types.ServiceVolumeConfig{ - Type: AzureFileDriverName, - Source: vi.name, - Target: vi.target, - ReadOnly: vi.readonly, - } - serviceConfigVolumes = append(serviceConfigVolumes, sv) - } - - return projectVolumes, serviceConfigVolumes, nil -} - -type volumeInput struct { - name string - storageAccount string - fileshare string - target string - readonly bool -} - -// parse takes a candidate string and creates a volumeInput -// Candidates take the form of [:][:] -// Source is of the form `/` -// If only the source is specified then the target is set to `/run/volumes/` -// Target is an absolute path in the container of the form `/path/to/mount` -// Permissions can only be set if the target is set -// If set, permissions must be `rw` or `ro` -func (v *volumeInput) parse(name string, candidate string) error { - v.name = name - - tokens := strings.Split(candidate, ":") - - sourceTokens := strings.Split(tokens[0], "/") - if len(sourceTokens) != 2 || sourceTokens[0] == "" { - return errors.Wrapf(api.ErrParsingFailed, "volume specification %q does not include a storage account before '/'", candidate) - } - if sourceTokens[1] == "" { - return errors.Wrapf(api.ErrParsingFailed, "volume specification %q does not include a storage file fileshare after '/'", candidate) - } - v.storageAccount = sourceTokens[0] - v.fileshare = sourceTokens[1] - - switch len(tokens) { - case 1: // source only - v.target = "/run/volumes/" + v.fileshare - case 2: // source and target - v.target = tokens[1] - case 3: // source, target, and permissions - v.target = tokens[1] - permissions := strings.ToLower(tokens[2]) - if permissions != "ro" && permissions != "rw" { - return errors.Wrapf(api.ErrParsingFailed, "volume specification %q has an invalid mode %q", candidate, permissions) - } - v.readonly = permissions == "ro" - default: - return errors.Wrapf(api.ErrParsingFailed, "volume specification %q has invalid format", candidate) - } - - return nil -} diff --git a/aci/convert/volume_test.go b/aci/convert/volume_test.go deleted file mode 100644 index 76e53c7aa..000000000 --- a/aci/convert/volume_test.go +++ /dev/null @@ -1,215 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package convert - -import ( - "context" - "strconv" - "testing" - - "github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2019-12-01/containerinstance" - "github.com/Azure/go-autorest/autorest/to" - "github.com/compose-spec/compose-go/types" - "github.com/stretchr/testify/mock" - "gotest.tools/v3/assert" - is "gotest.tools/v3/assert/cmp" -) - -func TestGetRunVolumes(t *testing.T) { - volumeStrings := []string{ - "myuser1/myshare1:/my/path/to/target1", - "myuser2/myshare2:/my/path/to/target2:ro", - "myuser3/myshare3:/my/path/to/target3:rw", - "myuser4/mydefaultsharename", // Use default placement at '/run/volumes/' - } - var goldenVolumeConfigs = map[string]types.VolumeConfig{ - "volume-0": getAzurefileVolumeConfig("volume-0", "myuser1", "myshare1", false), - "volume-1": getAzurefileVolumeConfig("volume-1", "myuser2", "myshare2", true), - "volume-2": getAzurefileVolumeConfig("volume-2", "myuser3", "myshare3", false), - "volume-3": getAzurefileVolumeConfig("volume-3", "myuser4", "mydefaultsharename", false), - } - goldenServiceVolumeConfigs := []types.ServiceVolumeConfig{ - getServiceVolumeConfig("volume-0", "/my/path/to/target1", false), - getServiceVolumeConfig("volume-1", "/my/path/to/target2", true), - getServiceVolumeConfig("volume-2", "/my/path/to/target3", false), - getServiceVolumeConfig("volume-3", "/run/volumes/mydefaultsharename", false), - } - - volumeConfigs, serviceVolumeConfigs, err := GetRunVolumes(volumeStrings) - assert.NilError(t, err) - for k, v := range volumeConfigs { - assert.DeepEqual(t, goldenVolumeConfigs[k], v) - } - for i, v := range serviceVolumeConfigs { - assert.DeepEqual(t, goldenServiceVolumeConfigs[i], v) - } -} - -func TestGetRunVolumesMissingFileShare(t *testing.T) { - _, _, err := GetRunVolumes([]string{"myaccount/"}) - assert.ErrorContains(t, err, "does not include a storage file fileshare after '/'") -} - -func TestGetRunVolumesMissingUser(t *testing.T) { - _, _, err := GetRunVolumes([]string{"/myshare"}) - assert.ErrorContains(t, err, "does not include a storage account before '/'") -} - -func TestGetRunVolumesNoShare(t *testing.T) { - _, _, err := GetRunVolumes([]string{"noshare"}) - assert.ErrorContains(t, err, "does not include a storage account before '/'") -} - -func TestGetRunVolumesInvalidOption(t *testing.T) { - _, _, err := GetRunVolumes([]string{"myuser4/myshare4:/my/path/to/target4:invalid"}) - assert.ErrorContains(t, err, `volume specification "myuser4/myshare4:/my/path/to/target4:invalid" has an invalid mode "invalid"`) -} - -func TestComposeVolumes(t *testing.T) { - ctx := context.TODO() - accountName := "myAccount" - mockStorageHelper.On("GetAzureStorageAccountKey", ctx, accountName).Return("123456", nil) - project := types.Project{ - Services: []types.ServiceConfig{ - { - Name: "service1", - Image: "image1", - }, - }, - Volumes: types.Volumes{ - "vol1": types.VolumeConfig{ - Driver: "azure_file", - DriverOpts: map[string]string{ - "share_name": "myFileshare", - "storage_account_name": accountName, - }, - }, - }, - } - - group, err := ToContainerGroup(ctx, convertCtx, project, mockStorageHelper) - assert.NilError(t, err) - - assert.Assert(t, is.Len(*group.Containers, 1)) - assert.Equal(t, *(*group.Containers)[0].Name, "service1") - expectedGroupVolume := containerinstance.Volume{ - Name: to.StringPtr("vol1"), - AzureFile: &containerinstance.AzureFileVolume{ - ShareName: to.StringPtr("myFileshare"), - StorageAccountName: &accountName, - StorageAccountKey: to.StringPtr("123456"), - ReadOnly: to.BoolPtr(false), - }, - } - assert.Equal(t, len(*group.Volumes), 1) - assert.DeepEqual(t, (*group.Volumes)[0], expectedGroupVolume) -} - -func TestPathVolumeErrorMessage(t *testing.T) { - ctx := context.TODO() - accountName := "myAccount" - mockStorageHelper.On("GetAzureStorageAccountKey", ctx, accountName).Return("123456", nil) - project := types.Project{ - Services: []types.ServiceConfig{ - { - Name: "service1", - Image: "image1", - Volumes: []types.ServiceVolumeConfig{ - { - Source: "/path", - Target: "/target", - Type: string(types.VolumeTypeBind), - }, - }, - }, - }, - } - - _, err := ToContainerGroup(ctx, convertCtx, project, mockStorageHelper) - assert.ErrorContains(t, err, `host path ("/path") not allowed as volume source, you need to reference an Azure File Share defined in the 'volumes' section`) -} - -func TestComposeVolumesRO(t *testing.T) { - ctx := context.TODO() - accountName := "myAccount" - mockStorageHelper.On("GetAzureStorageAccountKey", ctx, accountName).Return("123456", nil) - project := types.Project{ - Services: []types.ServiceConfig{ - { - Name: "service1", - Image: "image1", - }, - }, - Volumes: types.Volumes{ - "vol1": types.VolumeConfig{ - Driver: "azure_file", - DriverOpts: map[string]string{ - "share_name": "myFileshare", - "storage_account_name": accountName, - "read_only": "true", - }, - }, - }, - } - - group, err := ToContainerGroup(ctx, convertCtx, project, mockStorageHelper) - assert.NilError(t, err) - - assert.Assert(t, is.Len(*group.Containers, 1)) - assert.Equal(t, *(*group.Containers)[0].Name, "service1") - expectedGroupVolume := containerinstance.Volume{ - Name: to.StringPtr("vol1"), - AzureFile: &containerinstance.AzureFileVolume{ - ShareName: to.StringPtr("myFileshare"), - StorageAccountName: &accountName, - StorageAccountKey: to.StringPtr("123456"), - ReadOnly: to.BoolPtr(true), - }, - } - assert.Equal(t, len(*group.Volumes), 1) - assert.DeepEqual(t, (*group.Volumes)[0], expectedGroupVolume) -} - -type mockStorageLogin struct { - mock.Mock -} - -func (s *mockStorageLogin) GetAzureStorageAccountKey(ctx context.Context, accountName string) (string, error) { - args := s.Called(ctx, accountName) - return args.String(0), args.Error(1) -} - -func getServiceVolumeConfig(source string, target string, readOnly bool) types.ServiceVolumeConfig { - return types.ServiceVolumeConfig{ - Type: "azure_file", - Source: source, - Target: target, - ReadOnly: readOnly, - } -} - -func getAzurefileVolumeConfig(name string, accountNameKey string, shareNameKey string, readOnly bool) types.VolumeConfig { - return types.VolumeConfig{ - Name: name, - Driver: "azure_file", - DriverOpts: map[string]string{ - VolumeDriveroptsAccountNameKey: accountNameKey, - VolumeDriveroptsShareNameKey: shareNameKey, - volumeReadOnly: strconv.FormatBool(readOnly), - }, - } -} diff --git a/aci/e2e/aci-demo/aci_demo_port_secrets.yaml b/aci/e2e/aci-demo/aci_demo_port_secrets.yaml deleted file mode 100644 index ea1960158..000000000 --- a/aci/e2e/aci-demo/aci_demo_port_secrets.yaml +++ /dev/null @@ -1,24 +0,0 @@ -services: - db: - build: db - image: gtardif/sentences-db - - words: - build: words - image: gtardif/sentences-api - - web: - build: web - image: gtardif/sentences-web - ports: - - "80:80" - secrets: - - source: mysecret1 - target: mytarget1 - - mysecret2 - -secrets: - mysecret1: - file: ./my_secret1.txt - mysecret2: - file: ./my_secret2.txt diff --git a/aci/e2e/aci-demo/aci_demo_port_volumes.yaml b/aci/e2e/aci-demo/aci_demo_port_volumes.yaml deleted file mode 100644 index d19d4fd49..000000000 --- a/aci/e2e/aci-demo/aci_demo_port_volumes.yaml +++ /dev/null @@ -1,23 +0,0 @@ -services: - db: - build: db - image: gtardif/sentences-db - - words: - build: words - image: gtardif/sentences-api - - web: - build: web - image: gtardif/sentences-web - ports: - - "80:80" - volumes: - - mydata:/static/volume_test - -volumes: - mydata: - driver: azure_file - driver_opts: - share_name: dockertestshare - storage_account_name: dockertestvolumeaccount diff --git a/aci/e2e/aci-demo/db/Dockerfile b/aci/e2e/aci-demo/db/Dockerfile deleted file mode 100644 index 4065c93b3..000000000 --- a/aci/e2e/aci-demo/db/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright 2020 Docker Compose CLI authors - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -FROM postgres:10.0-alpine - -COPY words.sql /docker-entrypoint-initdb.d/ diff --git a/aci/e2e/aci-demo/db/words.sql b/aci/e2e/aci-demo/db/words.sql deleted file mode 100644 index ec13172c0..000000000 --- a/aci/e2e/aci-demo/db/words.sql +++ /dev/null @@ -1,55 +0,0 @@ -CREATE TABLE nouns (word TEXT NOT NULL); -CREATE TABLE verbs (word TEXT NOT NULL); -CREATE TABLE adjectives (word TEXT NOT NULL); - -INSERT INTO nouns(word) VALUES - ('cloud'), - ('elephant'), - ('gø language'), - ('laptøp'), - ('cøntainer'), - ('micrø-service'), - ('turtle'), - ('whale'), - ('gøpher'), - ('møby døck'), - ('server'), - ('bicycle'), - ('viking'), - ('mermaid'), - ('fjørd'), - ('legø'), - ('flødebolle'), - ('smørrebrød'); - -INSERT INTO verbs(word) VALUES - ('will drink'), - ('smashes'), - ('smøkes'), - ('eats'), - ('walks tøwards'), - ('løves'), - ('helps'), - ('pushes'), - ('debugs'), - ('invites'), - ('hides'), - ('will ship'); - -INSERT INTO adjectives(word) VALUES - ('the exquisite'), - ('a pink'), - ('the røtten'), - ('a red'), - ('the serverless'), - ('a brøken'), - ('a shiny'), - ('the pretty'), - ('the impressive'), - ('an awesøme'), - ('the famøus'), - ('a gigantic'), - ('the gløriøus'), - ('the nørdic'), - ('the welcøming'), - ('the deliciøus'); diff --git a/aci/e2e/aci-demo/demo_multi_port.yaml b/aci/e2e/aci-demo/demo_multi_port.yaml deleted file mode 100644 index 5fff56c71..000000000 --- a/aci/e2e/aci-demo/demo_multi_port.yaml +++ /dev/null @@ -1,16 +0,0 @@ -services: - db: - build: aci-demo/db - image: gtardif/sentences-db - - service1: - container_name: words - build: aci-demo/words - image: gtardif/sentences-api - ports: - - "8080:8080" - web: - build: aci-demo/web - image: gtardif/sentences-web - ports: - - "80:80" diff --git a/aci/e2e/aci-demo/img/dockercon-barcelona-logo.svg b/aci/e2e/aci-demo/img/dockercon-barcelona-logo.svg deleted file mode 100644 index 1c5a1b10b..000000000 --- a/aci/e2e/aci-demo/img/dockercon-barcelona-logo.svg +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/aci/e2e/aci-demo/img/dockercon_EU_17.jpg b/aci/e2e/aci-demo/img/dockercon_EU_17.jpg deleted file mode 100644 index 9223943c5..000000000 Binary files a/aci/e2e/aci-demo/img/dockercon_EU_17.jpg and /dev/null differ diff --git a/aci/e2e/aci-demo/my_secret1.txt b/aci/e2e/aci-demo/my_secret1.txt deleted file mode 100644 index 73a420f3f..000000000 --- a/aci/e2e/aci-demo/my_secret1.txt +++ /dev/null @@ -1 +0,0 @@ -myPassword1 diff --git a/aci/e2e/aci-demo/my_secret2.txt b/aci/e2e/aci-demo/my_secret2.txt deleted file mode 100644 index 8bf7f5754..000000000 --- a/aci/e2e/aci-demo/my_secret2.txt +++ /dev/null @@ -1 +0,0 @@ -another_password diff --git a/aci/e2e/aci-demo/web/Dockerfile b/aci/e2e/aci-demo/web/Dockerfile deleted file mode 100644 index 259809bf8..000000000 --- a/aci/e2e/aci-demo/web/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2020 Docker Compose CLI authors - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# BUILD -FROM golang:1.16-alpine AS build -COPY dispatcher.go . -RUN mkdir -p /out && go build -o /out/dispatcher dispatcher.go - -FROM alpine AS run -EXPOSE 80 -CMD ["/dispatcher"] -COPY static /static/ -COPY --from=build /out/dispatcher /dispatcher diff --git a/aci/e2e/aci-demo/web/dispatcher.go b/aci/e2e/aci-demo/web/dispatcher.go deleted file mode 100644 index c69ae7c2d..000000000 --- a/aci/e2e/aci-demo/web/dispatcher.go +++ /dev/null @@ -1,85 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package main - -import ( - "fmt" - "io/ioutil" - "log" - "math/rand" - "net" - "net/http" - "time" -) - -func main() { - rand.Seed(time.Now().UnixNano()) - - fwd := &forwarder{"words", 8080} - http.Handle("/words/", http.StripPrefix("/words", fwd)) - http.Handle("/", http.FileServer(http.Dir("static"))) - - fmt.Println("Listening on port 80") - err := http.ListenAndServe(":80", nil) - if err != nil { - fmt.Printf("Error while starting server: %v", err) - } -} - -type forwarder struct { - host string - port int -} - -func (f *forwarder) ServeHTTP(w http.ResponseWriter, r *http.Request) { - addrs, err := net.LookupHost(f.host) - if err != nil { - log.Println("Error", err) - http.Error(w, err.Error(), 500) - return - } - - log.Printf("%s %d available ips: %v", r.URL.Path, len(addrs), addrs) - ip := addrs[rand.Intn(len(addrs))] - log.Printf("%s I choose %s", r.URL.Path, ip) - - url := fmt.Sprintf("http://%s:%d%s", ip, f.port, r.URL.Path) - log.Printf("%s Calling %s", r.URL.Path, url) - - if err = copy(url, ip, w); err != nil { - log.Println("Error", err) - http.Error(w, err.Error(), 500) - return - } -} - -func copy(url, ip string, w http.ResponseWriter) error { - resp, err := http.Get(url) - if err != nil { - return err - } - - w.Header().Set("source", ip) - - buf, err := ioutil.ReadAll(resp.Body) - if err != nil { - return err - } - - _, err = w.Write(buf) - return err -} diff --git a/aci/e2e/aci-demo/web/static/angular.min.js b/aci/e2e/aci-demo/web/static/angular.min.js deleted file mode 100644 index ecdf96736..000000000 --- a/aci/e2e/aci-demo/web/static/angular.min.js +++ /dev/null @@ -1,311 +0,0 @@ -/* - AngularJS v1.5.3 - (c) 2010-2016 Google, Inc. http://angularjs.org - License: MIT -*/ -(function(T,P,u){'use strict';function O(a){return function(){var b=arguments[0],d;d="["+(a?a+":":"")+b+"] http://errors.angularjs.org/1.5.3/"+(a?a+"/":"")+b;for(b=1;b").append(a).html();try{return a[0].nodeType===Pa?N(d):d.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+N(b)})}catch(c){return N(d)}}function wc(a){try{return decodeURIComponent(a)}catch(b){}} -function xc(a){var b={};q((a||"").split("&"),function(a){var c,e,f;a&&(e=a=a.replace(/\+/g,"%20"),c=a.indexOf("="),-1!==c&&(e=a.substring(0,c),f=a.substring(c+1)),e=wc(e),A(e)&&(f=A(f)?wc(f):!0,va.call(b,e)?M(b[e])?b[e].push(f):b[e]=[b[e],f]:b[e]=f))});return b}function Sb(a){var b=[];q(a,function(a,c){M(a)?q(a,function(a){b.push(ja(c,!0)+(!0===a?"":"="+ja(a,!0)))}):b.push(ja(c,!0)+(!0===a?"":"="+ja(a,!0)))});return b.length?b.join("&"):""}function rb(a){return ja(a,!0).replace(/%26/gi,"&").replace(/%3D/gi, -"=").replace(/%2B/gi,"+")}function ja(a,b){return encodeURIComponent(a).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,b?"%20":"+")}function ce(a,b){var d,c,e=Qa.length;for(c=0;c/,">"));}b=b||[];b.unshift(["$provide",function(b){b.value("$rootElement",a)}]);d.debugInfoEnabled&&b.push(["$compileProvider",function(a){a.debugInfoEnabled(!0)}]);b.unshift("ng");c=eb(b,d.strictDi);c.invoke(["$rootScope", -"$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return c},e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;T&&e.test(T.name)&&(d.debugInfoEnabled=!0,T.name=T.name.replace(e,""));if(T&&!f.test(T.name))return c();T.name=T.name.replace(f,"");ea.resumeBootstrap=function(a){q(a,function(a){b.push(a)});return c()};D(ea.resumeDeferredBootstrap)&&ea.resumeDeferredBootstrap()}function ee(){T.name="NG_ENABLE_DEBUG_INFO!"+T.name;T.location.reload()} -function fe(a){a=ea.element(a).injector();if(!a)throw Ba("test");return a.get("$$testability")}function zc(a,b){b=b||"_";return a.replace(ge,function(a,c){return(c?b:"")+a.toLowerCase()})}function he(){var a;if(!Ac){var b=sb();($=z(b)?T.jQuery:b?T[b]:u)&&$.fn.on?(H=$,S($.fn,{scope:Ra.scope,isolateScope:Ra.isolateScope,controller:Ra.controller,injector:Ra.injector,inheritedData:Ra.inheritedData}),a=$.cleanData,$.cleanData=function(b){for(var c,e=0,f;null!=(f=b[e]);e++)(c=$._data(f,"events"))&&c.$destroy&& -$(f).triggerHandler("$destroy");a(b)}):H=U;ea.element=H;Ac=!0}}function tb(a,b,d){if(!a)throw Ba("areq",b||"?",d||"required");return a}function Sa(a,b,d){d&&M(a)&&(a=a[a.length-1]);tb(D(a),b,"not a function, got "+(a&&"object"===typeof a?a.constructor.name||"Object":typeof a));return a}function Ta(a,b){if("hasOwnProperty"===a)throw Ba("badname",b);}function Bc(a,b,d){if(!b)return a;b=b.split(".");for(var c,e=a,f=b.length,g=0;g")+c[2];for(c=c[0];c--;)d=d.lastChild;f=cb(f,d.childNodes);d=e.firstChild;d.textContent=""}else f.push(b.createTextNode(a));e.textContent="";e.innerHTML="";q(f,function(a){e.appendChild(a)});return e}function Mc(a, -b){var d=a.parentNode;d&&d.replaceChild(b,a);b.appendChild(a)}function U(a){if(a instanceof U)return a;var b;y(a)&&(a=W(a),b=!0);if(!(this instanceof U)){if(b&&"<"!=a.charAt(0))throw Vb("nosel");return new U(a)}if(b){b=P;var d;a=(d=Lf.exec(a))?[b.createElement(d[1])]:(d=Lc(a,b))?d.childNodes:[]}Nc(this,a)}function Wb(a){return a.cloneNode(!0)}function xb(a,b){b||gb(a);if(a.querySelectorAll)for(var d=a.querySelectorAll("*"),c=0,e=d.length;c=Da?!1:"function"===typeof a&&/^(?:class\s|constructor\()/.test(Function.prototype.toString.call(a));return d?(c.unshift(null),new (Function.prototype.bind.apply(a,c))):a.apply(b,c)},instantiate:function(a,b,c){var d=M(a)?a[a.length-1]:a;a=e(a,b,c);a.unshift(null);return new (Function.prototype.bind.apply(d,a))},get:d,annotate:eb.$$annotate,has:function(b){return n.hasOwnProperty(b+ -"Provider")||a.hasOwnProperty(b)}}}b=!0===b;var k={},l=[],m=new Ua([],!0),n={$provide:{provider:d(c),factory:d(f),service:d(function(a,b){return f(a,["$injector",function(a){return a.instantiate(b)}])}),value:d(function(a,b){return f(a,da(b),!1)}),constant:d(function(a,b){Ta(a,"constant");n[a]=b;F[a]=b}),decorator:function(a,b){var c=p.get(a+"Provider"),d=c.$get;c.$get=function(){var a=x.invoke(d,c);return x.invoke(b,null,{$delegate:a})}}}},p=n.$injector=h(n,function(a,b){ea.isString(b)&&l.push(b); -throw Ia("unpr",l.join(" <- "));}),F={},L=h(F,function(a,b){var c=p.get(a+"Provider",b);return x.invoke(c.$get,c,u,a)}),x=L;n.$injectorProvider={$get:da(L)};var r=g(a),x=L.get("$injector");x.strictDi=b;q(r,function(a){a&&x.invoke(a)});return x}function Ve(){var a=!0;this.disableAutoScrolling=function(){a=!1};this.$get=["$window","$location","$rootScope",function(b,d,c){function e(a){var b=null;Array.prototype.some.call(a,function(a){if("a"===oa(a))return b=a,!0});return b}function f(a){if(a){a.scrollIntoView(); -var c;c=g.yOffset;D(c)?c=c():Pb(c)?(c=c[0],c="fixed"!==b.getComputedStyle(c).position?0:c.getBoundingClientRect().bottom):R(c)||(c=0);c&&(a=a.getBoundingClientRect().top,b.scrollBy(0,a-c))}else b.scrollTo(0,0)}function g(a){a=y(a)?a:d.hash();var b;a?(b=h.getElementById(a))?f(b):(b=e(h.getElementsByName(a)))?f(b):"top"===a&&f(null):f(null)}var h=b.document;a&&c.$watch(function(){return d.hash()},function(a,b){a===b&&""===a||Nf(function(){c.$evalAsync(g)})});return g}]}function ib(a,b){if(!a&&!b)return""; -if(!a)return b;if(!b)return a;M(a)&&(a=a.join(" "));M(b)&&(b=b.join(" "));return a+" "+b}function Wf(a){y(a)&&(a=a.split(" "));var b=V();q(a,function(a){a.length&&(b[a]=!0)});return b}function Ja(a){return J(a)?a:{}}function Xf(a,b,d,c){function e(a){try{a.apply(null,Aa.call(arguments,1))}finally{if(L--,0===L)for(;x.length;)try{x.pop()()}catch(b){d.error(b)}}}function f(){t=null;g();h()}function g(){r=G();r=z(r)?null:r;na(r,I)&&(r=I);I=r}function h(){if(v!==k.url()||w!==r)v=k.url(),w=r,q(C,function(a){a(k.url(), -r)})}var k=this,l=a.location,m=a.history,n=a.setTimeout,p=a.clearTimeout,F={};k.isMock=!1;var L=0,x=[];k.$$completeOutstandingRequest=e;k.$$incOutstandingRequestCount=function(){L++};k.notifyWhenNoOutstandingRequests=function(a){0===L?a():x.push(a)};var r,w,v=l.href,Q=b.find("base"),t=null,G=c.history?function(){try{return m.state}catch(a){}}:E;g();w=r;k.url=function(b,d,e){z(e)&&(e=null);l!==a.location&&(l=a.location);m!==a.history&&(m=a.history);if(b){var f=w===e;if(v===b&&(!c.history||f))return k; -var h=v&&Ka(v)===Ka(b);v=b;w=e;if(!c.history||h&&f){if(!h||t)t=b;d?l.replace(b):h?(d=l,e=b.indexOf("#"),e=-1===e?"":b.substr(e),d.hash=e):l.href=b;l.href!==b&&(t=b)}else m[d?"replaceState":"pushState"](e,"",b),g(),w=r;return k}return t||l.href.replace(/%27/g,"'")};k.state=function(){return r};var C=[],K=!1,I=null;k.onUrlChange=function(b){if(!K){if(c.history)H(a).on("popstate",f);H(a).on("hashchange",f);K=!0}C.push(b);return b};k.$$applicationDestroyed=function(){H(a).off("hashchange popstate",f)}; -k.$$checkUrlChange=h;k.baseHref=function(){var a=Q.attr("href");return a?a.replace(/^(https?\:)?\/\/[^\/]*/,""):""};k.defer=function(a,b){var c;L++;c=n(function(){delete F[c];e(a)},b||0);F[c]=!0;return c};k.defer.cancel=function(a){return F[a]?(delete F[a],p(a),e(E),!0):!1}}function bf(){this.$get=["$window","$log","$sniffer","$document",function(a,b,d,c){return new Xf(a,c,b,d)}]}function cf(){this.$get=function(){function a(a,c){function e(a){a!=n&&(p?p==a&&(p=a.n):p=a,f(a.n,a.p),f(a,n),n=a,n.n= -null)}function f(a,b){a!=b&&(a&&(a.p=b),b&&(b.n=a))}if(a in b)throw O("$cacheFactory")("iid",a);var g=0,h=S({},c,{id:a}),k=V(),l=c&&c.capacity||Number.MAX_VALUE,m=V(),n=null,p=null;return b[a]={put:function(a,b){if(!z(b)){if(ll&&this.remove(p.key);return b}},get:function(a){if(l";b=la.firstChild.attributes;var d=b[0];b.removeNamedItem(d.name); -d.value=c;a.attributes.setNamedItem(d)}function B(a,b){try{a.addClass(b)}catch(c){}}function ba(a,b,c,d,e){a instanceof H||(a=H(a));for(var f=/\S+/,g=0,h=a.length;g").append(a).html())):c?Ra.clone.call(a):a;if(g)for(var h in g)d.data("$"+h+"Controller",g[h].instance);ba.$$addScopeInfo(d,b);c&&c(d,b);l&&l(b,d,d,f);return d}}function xa(a,b,c,d,e,f){function g(a,c,d,e){var f,k,l,m,n,p,G;if(r)for(G=Array(c.length),m=0;mB.priority)break;if(y=B.scope)B.templateUrl||(J(y)?(X("new/isolated scope",C||G,B,t),C=B):X("new/isolated scope",C,B,t)),G=G||B;L=B.name;if(!Ea&&(B.replace&&(B.templateUrl||B.template)||B.transclude&&!B.$$tlb)){for(y=ra+1;Ea=a[y++];)if(Ea.transclude&&!Ea.$$tlb||Ea.replace&&(Ea.templateUrl||Ea.template)){E=!0;break}Ea=!0}!B.templateUrl&& -B.controller&&(y=B.controller,v=v||V(),X("'"+L+"' controller",v[L],B,t),v[L]=B);if(y=B.transclude)if(K=!0,B.$$tlb||(X("transclusion",I,B,t),I=B),"element"==y)Ca=!0,p=B.priority,Q=t,t=d.$$element=H(ba.$$createComment(L,d[L])),b=t[0],da(f,Aa.call(Q,0),b),Q[0].$$parentNode=Q[0].parentNode,s=Zb(E,Q,e,p,g&&g.name,{nonTlbTranscludeDirective:I});else{var P=V();Q=H(Wb(b)).contents();if(J(y)){Q=[];var Z=V(),Y=V();q(y,function(a,b){var c="?"===a.charAt(0);a=c?a.substring(1):a;Z[a]=b;P[b]=null;Y[b]=c});q(t.contents(), -function(a){var b=Z[ya(oa(a))];b?(Y[b]=!0,P[b]=P[b]||[],P[b].push(a)):Q.push(a)});q(Y,function(a,b){if(!a)throw ga("reqslot",b);});for(var $ in P)P[$]&&(P[$]=Zb(E,P[$],e))}t.empty();s=Zb(E,Q,e,u,u,{needsNewScope:B.$$isolateScope||B.$$newScope});s.$$slots=P}if(B.template)if(x=!0,X("template",w,B,t),w=B,y=D(B.template)?B.template(t,d):B.template,y=ua(y),B.replace){g=B;Q=Ub.test(y)?Xc(ca(B.templateNamespace,W(y))):[];b=Q[0];if(1!=Q.length||1!==b.nodeType)throw ga("tplrt",L,"");da(f,t,b);N={$attr:{}}; -y=A(b,[],N);var ea=a.splice(ra+1,a.length-(ra+1));(C||G)&&Yc(y,C,G);a=a.concat(y).concat(ea);U(d,N);N=a.length}else t.html(y);if(B.templateUrl)x=!0,X("template",w,B,t),w=B,B.replace&&(g=B),n=aa(a.splice(ra,a.length-ra),t,d,f,K&&s,h,k,{controllerDirectives:v,newScopeDirective:G!==B&&G,newIsolateScopeDirective:C,templateDirective:w,nonTlbTranscludeDirective:I}),N=a.length;else if(B.compile)try{xa=B.compile(t,d,s),D(xa)?m(null,xa,R,Fa):xa&&m(xa.pre,xa.post,R,Fa)}catch(fa){c(fa,wa(t))}B.terminal&&(n.terminal= -!0,p=Math.max(p,B.priority))}n.scope=G&&!0===G.scope;n.transcludeOnThisElement=K;n.templateOnThisElement=x;n.transclude=s;l.hasElementTranscludeDirective=Ca;return n}function jb(a,b,c,d){var e;if(y(b)){var f=b.match(k);b=b.substring(f[0].length);var g=f[1]||f[3],f="?"===f[2];"^^"===g?c=c.parent():e=(e=d&&d[b])&&e.instance;if(!e){var h="$"+b+"Controller";e=g?c.inheritedData(h):c.data(h)}if(!e&&!f)throw ga("ctreq",b,a);}else if(M(b))for(e=[],g=0,f=b.length;gn.priority)&&-1!=n.restrict.indexOf(g)){l&&(n=Qb(n,{$$start:l,$$end:m}));if(!n.$$bindings){var v=n,C=n,w=n.name,B={isolateScope:null,bindToController:null};J(C.scope)&&(!0===C.bindToController?(B.bindToController=d(C.scope,w,!0),B.isolateScope={}):B.isolateScope=d(C.scope,w,!1));J(C.bindToController)&&(B.bindToController=d(C.bindToController,w,!0));if(J(B.bindToController)){var I=C.controller,K=C.controllerAs;if(!I)throw ga("noctrl", -w);if(!Uc(I,K))throw ga("noident",w);}var x=v.$$bindings=B;J(x.isolateScope)&&(n.$$isolateBindings=x.isolateScope)}b.push(n);k=n}}catch(t){c(t)}}return k}function R(b){if(e.hasOwnProperty(b))for(var c=a.get(b+"Directive"),d=0,f=c.length;d"+b+"";return c.childNodes[0].childNodes;default:return b}}function ea(a,b){if("srcdoc"==b)return G.HTML;var c=oa(a);if("xlinkHref"==b||"form"==c&&"action"==b||"img"!=c&&("src"==b||"ngSrc"==b))return G.RESOURCE_URL} -function fa(a,c,d,e,f){var g=ea(a,e);f=h[e]||f;var k=b(d,!0,g,f);if(k){if("multiple"===e&&"select"===oa(a))throw ga("selmulti",wa(a));c.push({priority:100,compile:function(){return{pre:function(a,c,h){c=h.$$observers||(h.$$observers=V());if(l.test(e))throw ga("nodomevents");var m=h[e];m!==d&&(k=m&&b(m,!0,g,f),d=m);k&&(h[e]=k(a),(c[e]||(c[e]=[])).$$inter=!0,(h.$$observers&&h.$$observers[e].$$scope||a).$watch(k,function(a,b){"class"===e&&a!=b?h.$updateClass(a,b):h.$set(e,a)}))}}}})}}function da(a,b, -c){var d=b[0],e=b.length,f=d.parentNode,g,h;if(a)for(g=0,h=a.length;g=b)return a;for(;b--;)8===a[b].nodeType&& -Yf.call(a,b,1);return a}function Uc(a,b){if(b&&y(b))return b;if(y(a)){var d=ad.exec(a);if(d)return d[3]}}function df(){var a={},b=!1;this.has=function(b){return a.hasOwnProperty(b)};this.register=function(b,c){Ta(b,"controller");J(b)?S(a,b):a[b]=c};this.allowGlobals=function(){b=!0};this.$get=["$injector","$window",function(d,c){function e(a,b,c,d){if(!a||!J(a.$scope))throw O("$controller")("noscp",d,b);a.$scope[b]=c}return function(f,g,h,k){var l,m,n;h=!0===h;k&&y(k)&&(n=k);if(y(f)){k=f.match(ad); -if(!k)throw Zf("ctrlfmt",f);m=k[1];n=n||k[3];f=a.hasOwnProperty(m)?a[m]:Bc(g.$scope,m,!0)||(b?Bc(c,m,!0):u);Sa(f,m,!0)}if(h)return h=(M(f)?f[f.length-1]:f).prototype,l=Object.create(h||null),n&&e(g,n,l,m||f.name),S(function(){var a=d.invoke(f,l,g,m);a!==l&&(J(a)||D(a))&&(l=a,n&&e(g,n,l,m||f.name));return l},{instance:l,identifier:n});l=d.instantiate(f,g,m);n&&e(g,n,l,m||f.name);return l}}]}function ef(){this.$get=["$window",function(a){return H(a.document)}]}function ff(){this.$get=["$log",function(a){return function(b, -d){a.error.apply(a,arguments)}}]}function $b(a){return J(a)?fa(a)?a.toISOString():db(a):a}function lf(){this.$get=function(){return function(a){if(!a)return"";var b=[];pc(a,function(a,c){null===a||z(a)||(M(a)?q(a,function(a){b.push(ja(c)+"="+ja($b(a)))}):b.push(ja(c)+"="+ja($b(a))))});return b.join("&")}}}function mf(){this.$get=function(){return function(a){function b(a,e,f){null===a||z(a)||(M(a)?q(a,function(a,c){b(a,e+"["+(J(a)?c:"")+"]")}):J(a)&&!fa(a)?pc(a,function(a,c){b(a,e+(f?"":"[")+c+(f? -"":"]"))}):d.push(ja(e)+"="+ja($b(a))))}if(!a)return"";var d=[];b(a,"",!0);return d.join("&")}}}function ac(a,b){if(y(a)){var d=a.replace($f,"").trim();if(d){var c=b("Content-Type");(c=c&&0===c.indexOf(bd))||(c=(c=d.match(ag))&&bg[c[0]].test(d));c&&(a=uc(d))}}return a}function cd(a){var b=V(),d;y(a)?q(a.split("\n"),function(a){d=a.indexOf(":");var e=N(W(a.substr(0,d)));a=W(a.substr(d+1));e&&(b[e]=b[e]?b[e]+", "+a:a)}):J(a)&&q(a,function(a,d){var f=N(d),g=W(a);f&&(b[f]=b[f]?b[f]+", "+g:g)});return b} -function dd(a){var b;return function(d){b||(b=cd(a));return d?(d=b[N(d)],void 0===d&&(d=null),d):b}}function ed(a,b,d,c){if(D(c))return c(a,b,d);q(c,function(c){a=c(a,b,d)});return a}function kf(){var a=this.defaults={transformResponse:[ac],transformRequest:[function(a){return J(a)&&"[object File]"!==ka.call(a)&&"[object Blob]"!==ka.call(a)&&"[object FormData]"!==ka.call(a)?db(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:ia(bc),put:ia(bc),patch:ia(bc)},xsrfCookieName:"XSRF-TOKEN", -xsrfHeaderName:"X-XSRF-TOKEN",paramSerializer:"$httpParamSerializer"},b=!1;this.useApplyAsync=function(a){return A(a)?(b=!!a,this):b};var d=!0;this.useLegacyPromiseExtensions=function(a){return A(a)?(d=!!a,this):d};var c=this.interceptors=[];this.$get=["$httpBackend","$$cookieReader","$cacheFactory","$rootScope","$q","$injector",function(e,f,g,h,k,l){function m(b){function c(a){var b=S({},a);b.data=ed(a.data,a.headers,a.status,f.transformResponse);a=a.status;return 200<=a&&300>a?b:k.reject(b)}function e(a, -b){var c,d={};q(a,function(a,e){D(a)?(c=a(b),null!=c&&(d[e]=c)):d[e]=a});return d}if(!J(b))throw O("$http")("badreq",b);if(!y(b.url))throw O("$http")("badreq",b.url);var f=S({method:"get",transformRequest:a.transformRequest,transformResponse:a.transformResponse,paramSerializer:a.paramSerializer},b);f.headers=function(b){var c=a.headers,d=S({},b.headers),f,g,h,c=S({},c.common,c[N(b.method)]);a:for(f in c){g=N(f);for(h in d)if(N(h)===g)continue a;d[f]=c[f]}return e(d,ia(b))}(b);f.method=vb(f.method); -f.paramSerializer=y(f.paramSerializer)?l.get(f.paramSerializer):f.paramSerializer;var g=[function(b){var d=b.headers,e=ed(b.data,dd(d),u,b.transformRequest);z(e)&&q(d,function(a,b){"content-type"===N(b)&&delete d[b]});z(b.withCredentials)&&!z(a.withCredentials)&&(b.withCredentials=a.withCredentials);return n(b,e).then(c,c)},u],h=k.when(f);for(q(L,function(a){(a.request||a.requestError)&&g.unshift(a.request,a.requestError);(a.response||a.responseError)&&g.push(a.response,a.responseError)});g.length;){b= -g.shift();var m=g.shift(),h=h.then(b,m)}d?(h.success=function(a){Sa(a,"fn");h.then(function(b){a(b.data,b.status,b.headers,f)});return h},h.error=function(a){Sa(a,"fn");h.then(null,function(b){a(b.data,b.status,b.headers,f)});return h}):(h.success=fd("success"),h.error=fd("error"));return h}function n(c,d){function g(a,c,d,e){function f(){l(c,a,d,e)}K&&(200<=a&&300>a?K.put(L,[a,c,cd(d),e]):K.remove(L));b?h.$applyAsync(f):(f(),h.$$phase||h.$apply())}function l(a,b,d,e){b=-1<=b?b:0;(200<=b&&300>b?G.resolve: -G.reject)({data:a,status:b,headers:dd(d),config:c,statusText:e})}function n(a){l(a.data,a.status,ia(a.headers()),a.statusText)}function t(){var a=m.pendingRequests.indexOf(c);-1!==a&&m.pendingRequests.splice(a,1)}var G=k.defer(),C=G.promise,K,I,qa=c.headers,L=p(c.url,c.paramSerializer(c.params));m.pendingRequests.push(c);C.then(t,t);!c.cache&&!a.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(K=J(c.cache)?c.cache:J(a.cache)?a.cache:F);K&&(I=K.get(L),A(I)?I&&D(I.then)?I.then(n,n):M(I)? -l(I[1],I[0],ia(I[2]),I[3]):l(I,200,{},"OK"):K.put(L,C));z(I)&&((I=gd(c.url)?f()[c.xsrfCookieName||a.xsrfCookieName]:u)&&(qa[c.xsrfHeaderName||a.xsrfHeaderName]=I),e(c.method,L,d,g,qa,c.timeout,c.withCredentials,c.responseType));return C}function p(a,b){0=l&&(v.resolve(r),x(Q.$$intervalId),delete g[Q.$$intervalId]);w||a.$apply()},k);g[Q.$$intervalId]=v;return Q}var g={};f.cancel=function(a){return a&&a.$$intervalId in g?(g[a.$$intervalId].reject("canceled"),b.clearInterval(a.$$intervalId), -delete g[a.$$intervalId],!0):!1};return f}]}function cc(a){a=a.split("/");for(var b=a.length;b--;)a[b]=rb(a[b]);return a.join("/")}function hd(a,b){var d=sa(a);b.$$protocol=d.protocol;b.$$host=d.hostname;b.$$port=Y(d.port)||dg[d.protocol]||null}function id(a,b){var d="/"!==a.charAt(0);d&&(a="/"+a);var c=sa(a);b.$$path=decodeURIComponent(d&&"/"===c.pathname.charAt(0)?c.pathname.substring(1):c.pathname);b.$$search=xc(c.search);b.$$hash=decodeURIComponent(c.hash);b.$$path&&"/"!=b.$$path.charAt(0)&&(b.$$path= -"/"+b.$$path)}function la(a,b){if(0===b.indexOf(a))return b.substr(a.length)}function Ka(a){var b=a.indexOf("#");return-1==b?a:a.substr(0,b)}function kb(a){return a.replace(/(#.+)|#$/,"$1")}function dc(a,b,d){this.$$html5=!0;d=d||"";hd(a,this);this.$$parse=function(a){var d=la(b,a);if(!y(d))throw Fb("ipthprfx",a,b);id(d,this);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Sb(this.$$search),d=this.$$hash?"#"+rb(this.$$hash):"";this.$$url=cc(this.$$path)+(a?"?"+a:"")+ -d;this.$$absUrl=b+this.$$url.substr(1)};this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;A(f=la(a,c))?(g=f,g=A(f=la(d,f))?b+(la("/",f)||f):a+g):A(f=la(b,c))?g=b+f:b==c+"/"&&(g=b);g&&this.$$parse(g);return!!g}}function ec(a,b,d){hd(a,this);this.$$parse=function(c){var e=la(a,c)||la(b,c),f;z(e)||"#"!==e.charAt(0)?this.$$html5?f=e:(f="",z(e)&&(a=c,this.replace())):(f=la(d,e),z(f)&&(f=e));id(f,this);c=this.$$path;var e=a,g=/^\/[A-Z]:(\/.*)/;0===f.indexOf(e)&& -(f=f.replace(e,""));g.exec(f)||(c=(f=g.exec(c))?f[1]:c);this.$$path=c;this.$$compose()};this.$$compose=function(){var b=Sb(this.$$search),e=this.$$hash?"#"+rb(this.$$hash):"";this.$$url=cc(this.$$path)+(b?"?"+b:"")+e;this.$$absUrl=a+(this.$$url?d+this.$$url:"")};this.$$parseLinkUrl=function(b,d){return Ka(a)==Ka(b)?(this.$$parse(b),!0):!1}}function jd(a,b,d){this.$$html5=!0;ec.apply(this,arguments);this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;a==Ka(c)? -f=c:(g=la(b,c))?f=a+d+g:b===c+"/"&&(f=b);f&&this.$$parse(f);return!!f};this.$$compose=function(){var b=Sb(this.$$search),e=this.$$hash?"#"+rb(this.$$hash):"";this.$$url=cc(this.$$path)+(b?"?"+b:"")+e;this.$$absUrl=a+d+this.$$url}}function Gb(a){return function(){return this[a]}}function kd(a,b){return function(d){if(z(d))return this[a];this[a]=b(d);this.$$compose();return this}}function pf(){var a="",b={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(b){return A(b)?(a=b,this): -a};this.html5Mode=function(a){return Oa(a)?(b.enabled=a,this):J(a)?(Oa(a.enabled)&&(b.enabled=a.enabled),Oa(a.requireBase)&&(b.requireBase=a.requireBase),Oa(a.rewriteLinks)&&(b.rewriteLinks=a.rewriteLinks),this):b};this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function(d,c,e,f,g){function h(a,b,d){var e=l.url(),f=l.$$state;try{c.url(a,b,d),l.$$state=c.state()}catch(g){throw l.url(e),l.$$state=f,g;}}function k(a,b){d.$broadcast("$locationChangeSuccess",l.absUrl(),a,l.$$state, -b)}var l,m;m=c.baseHref();var n=c.url(),p;if(b.enabled){if(!m&&b.requireBase)throw Fb("nobase");p=n.substring(0,n.indexOf("/",n.indexOf("//")+2))+(m||"/");m=e.history?dc:jd}else p=Ka(n),m=ec;var F=p.substr(0,Ka(p).lastIndexOf("/")+1);l=new m(p,F,"#"+a);l.$$parseLinkUrl(n,n);l.$$state=c.state();var q=/^\s*(javascript|mailto):/i;f.on("click",function(a){if(b.rewriteLinks&&!a.ctrlKey&&!a.metaKey&&!a.shiftKey&&2!=a.which&&2!=a.button){for(var e=H(a.target);"a"!==oa(e[0]);)if(e[0]===f[0]||!(e=e.parent())[0])return; -var h=e.prop("href"),k=e.attr("href")||e.attr("xlink:href");J(h)&&"[object SVGAnimatedString]"===h.toString()&&(h=sa(h.animVal).href);q.test(h)||!h||e.attr("target")||a.isDefaultPrevented()||!l.$$parseLinkUrl(h,k)||(a.preventDefault(),l.absUrl()!=c.url()&&(d.$apply(),g.angular["ff-684208-preventDefault"]=!0))}});kb(l.absUrl())!=kb(n)&&c.url(l.absUrl(),!0);var x=!0;c.onUrlChange(function(a,b){z(la(F,a))?g.location.href=a:(d.$evalAsync(function(){var c=l.absUrl(),e=l.$$state,f;a=kb(a);l.$$parse(a); -l.$$state=b;f=d.$broadcast("$locationChangeStart",a,c,b,e).defaultPrevented;l.absUrl()===a&&(f?(l.$$parse(c),l.$$state=e,h(c,!1,e)):(x=!1,k(c,e)))}),d.$$phase||d.$digest())});d.$watch(function(){var a=kb(c.url()),b=kb(l.absUrl()),f=c.state(),g=l.$$replace,m=a!==b||l.$$html5&&e.history&&f!==l.$$state;if(x||m)x=!1,d.$evalAsync(function(){var b=l.absUrl(),c=d.$broadcast("$locationChangeStart",b,a,l.$$state,f).defaultPrevented;l.absUrl()===b&&(c?(l.$$parse(a),l.$$state=f):(m&&h(b,g,f===l.$$state?null: -l.$$state),k(a,f)))});l.$$replace=!1});return l}]}function qf(){var a=!0,b=this;this.debugEnabled=function(b){return A(b)?(a=b,this):a};this.$get=["$window",function(d){function c(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=d.console||{},e=b[a]||b.log||E;a=!1;try{a=!!e.apply}catch(k){}return a?function(){var a=[];q(arguments,function(b){a.push(c(b))}); -return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){a&&c.apply(b,arguments)}}()}}]}function Wa(a,b){if("__defineGetter__"===a||"__defineSetter__"===a||"__lookupGetter__"===a||"__lookupSetter__"===a||"__proto__"===a)throw ca("isecfld",b);return a}function eg(a){return a+""}function ta(a,b){if(a){if(a.constructor===a)throw ca("isecfn",b);if(a.window===a)throw ca("isecwindow",b);if(a.children&& -(a.nodeName||a.prop&&a.attr&&a.find))throw ca("isecdom",b);if(a===Object)throw ca("isecobj",b);}return a}function ld(a,b){if(a){if(a.constructor===a)throw ca("isecfn",b);if(a===fg||a===gg||a===hg)throw ca("isecff",b);}}function Hb(a,b){if(a&&(a===(0).constructor||a===(!1).constructor||a==="".constructor||a==={}.constructor||a===[].constructor||a===Function.constructor))throw ca("isecaf",b);}function ig(a,b){return"undefined"!==typeof a?a:b}function md(a,b){return"undefined"===typeof a?b:"undefined"=== -typeof b?a:a+b}function aa(a,b){var d,c;switch(a.type){case s.Program:d=!0;q(a.body,function(a){aa(a.expression,b);d=d&&a.expression.constant});a.constant=d;break;case s.Literal:a.constant=!0;a.toWatch=[];break;case s.UnaryExpression:aa(a.argument,b);a.constant=a.argument.constant;a.toWatch=a.argument.toWatch;break;case s.BinaryExpression:aa(a.left,b);aa(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.left.toWatch.concat(a.right.toWatch);break;case s.LogicalExpression:aa(a.left, -b);aa(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.constant?[]:[a];break;case s.ConditionalExpression:aa(a.test,b);aa(a.alternate,b);aa(a.consequent,b);a.constant=a.test.constant&&a.alternate.constant&&a.consequent.constant;a.toWatch=a.constant?[]:[a];break;case s.Identifier:a.constant=!1;a.toWatch=[a];break;case s.MemberExpression:aa(a.object,b);a.computed&&aa(a.property,b);a.constant=a.object.constant&&(!a.computed||a.property.constant);a.toWatch=[a];break;case s.CallExpression:d= -a.filter?!b(a.callee.name).$stateful:!1;c=[];q(a.arguments,function(a){aa(a,b);d=d&&a.constant;a.constant||c.push.apply(c,a.toWatch)});a.constant=d;a.toWatch=a.filter&&!b(a.callee.name).$stateful?c:[a];break;case s.AssignmentExpression:aa(a.left,b);aa(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=[a];break;case s.ArrayExpression:d=!0;c=[];q(a.elements,function(a){aa(a,b);d=d&&a.constant;a.constant||c.push.apply(c,a.toWatch)});a.constant=d;a.toWatch=c;break;case s.ObjectExpression:d= -!0;c=[];q(a.properties,function(a){aa(a.value,b);d=d&&a.value.constant;a.value.constant||c.push.apply(c,a.value.toWatch)});a.constant=d;a.toWatch=c;break;case s.ThisExpression:a.constant=!1;a.toWatch=[];break;case s.LocalsExpression:a.constant=!1,a.toWatch=[]}}function nd(a){if(1==a.length){a=a[0].expression;var b=a.toWatch;return 1!==b.length?b:b[0]!==a?b:u}}function od(a){return a.type===s.Identifier||a.type===s.MemberExpression}function pd(a){if(1===a.body.length&&od(a.body[0].expression))return{type:s.AssignmentExpression, -left:a.body[0].expression,right:{type:s.NGValueParameter},operator:"="}}function qd(a){return 0===a.body.length||1===a.body.length&&(a.body[0].expression.type===s.Literal||a.body[0].expression.type===s.ArrayExpression||a.body[0].expression.type===s.ObjectExpression)}function rd(a,b){this.astBuilder=a;this.$filter=b}function sd(a,b){this.astBuilder=a;this.$filter=b}function Ib(a){return"constructor"==a}function fc(a){return D(a.valueOf)?a.valueOf():jg.call(a)}function rf(){var a=V(),b=V(),d={"true":!0, -"false":!1,"null":null,undefined:u};this.addLiteral=function(a,b){d[a]=b};this.$get=["$filter",function(c){function e(d,e,g){var p,t,G;g=g||x;switch(typeof d){case "string":G=d=d.trim();var C=g?b:a;p=C[G];if(!p){":"===d.charAt(0)&&":"===d.charAt(1)&&(t=!0,d=d.substring(2));p=g?L:F;var K=new gc(p);p=(new hc(K,c,p)).parse(d);p.constant?p.$$watchDelegate=m:t?p.$$watchDelegate=p.literal?l:k:p.inputs&&(p.$$watchDelegate=h);g&&(p=f(p));C[G]=p}return n(p,e);case "function":return n(d,e);default:return n(E, -e)}}function f(a){function b(c,d,e,f){var g=x;x=!0;try{return a(c,d,e,f)}finally{x=g}}if(!a)return a;b.$$watchDelegate=a.$$watchDelegate;b.assign=f(a.assign);b.constant=a.constant;b.literal=a.literal;for(var c=0;a.inputs&&c=this.promise.$$state.status&& -d&&d.length&&a(function(){for(var a,e,f=0,g=d.length;f -a)for(b in l++,f)va.call(e,b)||(v--,delete f[b])}else f!==e&&(f=e,l++);return l}}c.$stateful=!0;var d=this,e,f,h,k=1t&&(z=4-t,A[z]||(A[z]=[]),A[z].push({msg:D(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):a.exp,newVal:g,oldVal:k}));else if(a===c){q=!1;break a}}catch(H){f(H)}if(!(p=F.$$watchersCount&& -F.$$childHead||F!==this&&F.$$nextSibling))for(;F!==this&&!(p=F.$$nextSibling);)F=F.$parent}while(F=p);if((q||v.length)&&!t--)throw w.$$phase=null,d("infdig",b,A);}while(q||v.length);for(w.$$phase=null;u.length;)try{u.shift()()}catch(J){f(J)}},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;this===w&&h.$$applicationDestroyed();p(this,-this.$$watchersCount);for(var b in this.$$listenerCount)F(this,this.$$listenerCount[b],b);a&&a.$$childHead== -this&&(a.$$childHead=this.$$nextSibling);a&&a.$$childTail==this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling);this.$destroy=this.$digest=this.$apply=this.$evalAsync=this.$applyAsync=E;this.$on=this.$watch=this.$watchGroup=function(){return E};this.$$listeners={};this.$$nextSibling=null;l(this)}},$eval:function(a,b){return g(a)(this,b)},$evalAsync:function(a,b){w.$$phase|| -v.length||h.defer(function(){v.length&&w.$digest()});v.push({scope:this,expression:g(a),locals:b})},$$postDigest:function(a){u.push(a)},$apply:function(a){try{n("$apply");try{return this.$eval(a)}finally{w.$$phase=null}}catch(b){f(b)}finally{try{w.$digest()}catch(c){throw f(c),c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&t.push(b);a=g(a);r()},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]||(d.$$listenerCount[a]= -0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){var d=c.indexOf(b);-1!==d&&(c[d]=null,F(e,1,a))}},$emit:function(a,b){var c=[],d,e=this,g=!1,h={name:a,targetScope:e,stopPropagation:function(){g=!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1},k=cb([h],arguments,1),l,m;do{d=e.$$listeners[a]||c;h.currentScope=e;l=0;for(m=d.length;lDa)throw ua("iequirks");var c=ia(ma);c.isEnabled=function(){return a};c.trustAs=d.trustAs;c.getTrusted=d.getTrusted;c.valueOf=d.valueOf;a||(c.trustAs=c.getTrusted=function(a,b){return b}, -c.valueOf=$a);c.parseAs=function(a,d){var e=b(d);return e.literal&&e.constant?e:b(d,function(b){return c.getTrusted(a,b)})};var e=c.parseAs,f=c.getTrusted,g=c.trustAs;q(ma,function(a,b){var d=N(b);c[fb("parse_as_"+d)]=function(b){return e(a,b)};c[fb("get_trusted_"+d)]=function(b){return f(a,b)};c[fb("trust_as_"+d)]=function(b){return g(a,b)}});return c}]}function xf(){this.$get=["$window","$document",function(a,b){var d={},c=!(a.chrome&&a.chrome.app&&a.chrome.app.runtime)&&a.history&&a.history.pushState, -e=Y((/android (\d+)/.exec(N((a.navigator||{}).userAgent))||[])[1]),f=/Boxee/i.test((a.navigator||{}).userAgent),g=b[0]||{},h,k=/^(Moz|webkit|ms)(?=[A-Z])/,l=g.body&&g.body.style,m=!1,n=!1;if(l){for(var p in l)if(m=k.exec(p)){h=m[0];h=h.substr(0,1).toUpperCase()+h.substr(1);break}h||(h="WebkitOpacity"in l&&"webkit");m=!!("transition"in l||h+"Transition"in l);n=!!("animation"in l||h+"Animation"in l);!e||m&&n||(m=y(l.webkitTransition),n=y(l.webkitAnimation))}return{history:!(!c||4>e||f),hasEvent:function(a){if("input"=== -a&&11>=Da)return!1;if(z(d[a])){var b=g.createElement("div");d[a]="on"+a in b}return d[a]},csp:Ga(),vendorPrefix:h,transitions:m,animations:n,android:e}}]}function zf(){var a;this.httpOptions=function(b){return b?(a=b,this):a};this.$get=["$templateCache","$http","$q","$sce",function(b,d,c,e){function f(g,h){f.totalPendingRequests++;y(g)&&b.get(g)||(g=e.getTrustedResourceUrl(g));var k=d.defaults&&d.defaults.transformResponse;M(k)?k=k.filter(function(a){return a!==ac}):k===ac&&(k=null);return d.get(g, -S({cache:b,transformResponse:k},a))["finally"](function(){f.totalPendingRequests--}).then(function(a){b.put(g,a.data);return a.data},function(a){if(!h)throw lg("tpload",g,a.status,a.statusText);return c.reject(a)})}f.totalPendingRequests=0;return f}]}function Af(){this.$get=["$rootScope","$browser","$location",function(a,b,d){return{findBindings:function(a,b,d){a=a.getElementsByClassName("ng-binding");var g=[];q(a,function(a){var c=ea.element(a).data("$binding");c&&q(c,function(c){d?(new RegExp("(^|\\s)"+ -ud(b)+"(\\s|\\||$)")).test(c)&&g.push(a):-1!=c.indexOf(b)&&g.push(a)})});return g},findModels:function(a,b,d){for(var g=["ng-","data-ng-","ng\\:"],h=0;hc&&(c=e),c+=+a.slice(e+1),a=a.substring(0,e)):0>c&&(c=a.length);for(e=0;a.charAt(e)==jc;e++);if(e==(g=a.length))d=[0],c=1;else{for(g--;a.charAt(g)==jc;)g--;c-=e;d=[];for(f=0;e<=g;e++,f++)d[f]=+a.charAt(e)}c>Ed&&(d=d.splice(0,Ed-1),b=c-1,c=1);return{d:d,e:b,i:c}}function tg(a,b,d,c){var e=a.d,f=e.length-a.i;b=z(b)?Math.min(Math.max(d,f),c):+b;d=b+a.i;c=e[d];if(0d-1){for(c=0;c>d;c--)e.unshift(0),a.i++;e.unshift(1);a.i++}else e[d-1]++;for(;fh;)k.unshift(0),h++;0=b.lgSize&&h.unshift(k.splice(-b.lgSize).join(""));k.length>b.gSize;)h.unshift(k.splice(-b.gSize).join(""));k.length&&h.unshift(k.join(""));k=h.join(d);f.length&&(k+=c+f.join(""));e&&(k+="e+"+e)}return 0>a&&!g?b.negPre+k+b.negSuf:b.posPre+k+b.posSuf}function Jb(a,b,d,c){var e="";if(0>a||c&&0>=a)c?a=-a+1:(a=-a,e="-");for(a=""+a;a.length-d)f+=d;0===f&&-12==d&&(f=12);return Jb(f,b,c,e)}}function lb(a,b,d){return function(c,e){var f=c["get"+a](),g=vb((d?"STANDALONE":"")+(b?"SHORT":"")+a);return e[g][f]}}function Fd(a){var b=(new Date(a,0,1)).getDay();return new Date(a,0,(4>=b?5:12)-b)}function Gd(a){return function(b){var d=Fd(b.getFullYear());b=+new Date(b.getFullYear(),b.getMonth(),b.getDate()+(4-b.getDay()))-+d;b=1+Math.round(b/6048E5);return Jb(b,a)}}function kc(a,b){return 0>=a.getFullYear()? -b.ERAS[0]:b.ERAS[1]}function zd(a){function b(a){var b;if(b=a.match(d)){a=new Date(0);var f=0,g=0,h=b[8]?a.setUTCFullYear:a.setFullYear,k=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=Y(b[9]+b[10]),g=Y(b[9]+b[11]));h.call(a,Y(b[1]),Y(b[2])-1,Y(b[3]));f=Y(b[4]||0)-f;g=Y(b[5]||0)-g;h=Y(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));k.call(a,f,g,h,b)}return a}var d=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,d,f){var g="",h= -[],k,l;d=d||"mediumDate";d=a.DATETIME_FORMATS[d]||d;y(c)&&(c=ug.test(c)?Y(c):b(c));R(c)&&(c=new Date(c));if(!fa(c)||!isFinite(c.getTime()))return c;for(;d;)(l=vg.exec(d))?(h=cb(h,l,1),d=h.pop()):(h.push(d),d=null);var m=c.getTimezoneOffset();f&&(m=vc(f,m),c=Rb(c,f,!0));q(h,function(b){k=wg[b];g+=k?k(c,a.DATETIME_FORMATS,m):"''"===b?"'":b.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function ng(){return function(a,b){z(b)&&(b=2);return db(a,b)}}function og(){return function(a,b,d){b=Infinity=== -Math.abs(Number(b))?Number(b):Y(b);if(isNaN(b))return a;R(a)&&(a=a.toString());if(!M(a)&&!y(a))return a;d=!d||isNaN(d)?0:Y(d);d=0>d?Math.max(0,a.length+d):d;return 0<=b?a.slice(d,d+b):0===d?a.slice(b,a.length):a.slice(Math.max(0,d+b),d)}}function Bd(a){function b(b,d){d=d?-1:1;return b.map(function(b){var c=1,h=$a;if(D(b))h=b;else if(y(b)){if("+"==b.charAt(0)||"-"==b.charAt(0))c="-"==b.charAt(0)?-1:1,b=b.substring(1);if(""!==b&&(h=a(b),h.constant))var k=h(),h=function(a){return a[k]}}return{get:h, -descending:c*d}})}function d(a){switch(typeof a){case "number":case "boolean":case "string":return!0;default:return!1}}return function(a,e,f){if(null==a)return a;if(!za(a))throw O("orderBy")("notarray",a);M(e)||(e=[e]);0===e.length&&(e=["+"]);var g=b(e,f);g.push({get:function(){return{}},descending:f?-1:1});a=Array.prototype.map.call(a,function(a,b){return{value:a,predicateValues:g.map(function(c){var e=c.get(a);c=typeof e;if(null===e)c="string",e="null";else if("string"===c)e=e.toLowerCase();else if("object"=== -c)a:{if("function"===typeof e.valueOf&&(e=e.valueOf(),d(e)))break a;if(rc(e)&&(e=e.toString(),d(e)))break a;e=b}return{value:e,type:c}})}});a.sort(function(a,b){for(var c=0,d=0,e=g.length;db||37<=b&&40>=b|| -m(a,this,this.value)});if(e.hasEvent("paste"))b.on("paste cut",m)}b.on("change",l);if(Jd[g]&&c.$$hasNativeValidators&&g===d.type)b.on("keydown wheel mousedown",function(a){if(!k){var b=this.validity,c=b.badInput,d=b.typeMismatch;k=f.defer(function(){k=null;b.badInput===c&&b.typeMismatch===d||l(a)})}});c.$render=function(){var a=c.$isEmpty(c.$viewValue)?"":c.$viewValue;b.val()!==a&&b.val(a)}}function Mb(a,b){return function(d,c){var e,f;if(fa(d))return d;if(y(d)){'"'==d.charAt(0)&&'"'==d.charAt(d.length- -1)&&(d=d.substring(1,d.length-1));if(xg.test(d))return new Date(d);a.lastIndex=0;if(e=a.exec(d))return e.shift(),f=c?{yyyy:c.getFullYear(),MM:c.getMonth()+1,dd:c.getDate(),HH:c.getHours(),mm:c.getMinutes(),ss:c.getSeconds(),sss:c.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0,sss:0},q(e,function(a,c){c=x};g.$observe("min",function(a){x= -p(a);h.$validate()})}if(A(g.max)||g.ngMax){var r;h.$validators.max=function(a){return!n(a)||z(r)||d(a)<=r};g.$observe("max",function(a){r=p(a);h.$validate()})}}}function Kd(a,b,d,c){(c.$$hasNativeValidators=J(b[0].validity))&&c.$parsers.push(function(a){var c=b.prop("validity")||{};return c.badInput||c.typeMismatch?u:a})}function Ld(a,b,d,c,e){if(A(c)){a=a(c);if(!a.constant)throw ob("constexpr",d,c);return a(b)}return e}function mc(a,b){a="ngClass"+a;return["$animate",function(d){function c(a,b){var c= -[],d=0;a:for(;d(?:<\/\1>|)$/,Ub=/<|&#?\w+;/,Jf=/<([\w:-]+)/,Kf=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,ha={option:[1,'"],thead:[1,"","
"],col:[2, -"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ha.optgroup=ha.option;ha.tbody=ha.tfoot=ha.colgroup=ha.caption=ha.thead;ha.th=ha.td;var Rf=Node.prototype.contains||function(a){return!!(this.compareDocumentPosition(a)&16)},Ra=U.prototype={ready:function(a){function b(){d||(d=!0,a())}var d=!1;"complete"===P.readyState?setTimeout(b):(this.on("DOMContentLoaded",b),U(T).on("load",b))},toString:function(){var a= -[];q(this,function(b){a.push(""+b)});return"["+a.join(", ")+"]"},eq:function(a){return 0<=a?H(this[a]):H(this[this.length+a])},length:0,push:zg,sort:[].sort,splice:[].splice},Eb={};q("multiple selected checked disabled readOnly required open".split(" "),function(a){Eb[N(a)]=a});var Sc={};q("input select option textarea button form details".split(" "),function(a){Sc[a]=!0});var $c={ngMinlength:"minlength",ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern"};q({data:Xb,removeData:gb, -hasData:function(a){for(var b in hb[a.ng339])return!0;return!1},cleanData:function(a){for(var b=0,d=a.length;b/,Uf=/^[^\(]*\(\s*([^\)]*)\)/m,Ag=/,/,Bg=/^\s*(_?)(\S+?)\1\s*$/,Sf=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Ia=O("$injector");eb.$$annotate=function(a,b,d){var c;if("function"===typeof a){if(!(c=a.$inject)){c=[];if(a.length){if(b)throw y(d)&&d||(d=a.name||Vf(a)),Ia("strictdi",d);b=Tc(a);q(b[1].split(Ag),function(a){a.replace(Bg,function(a,b,d){c.push(d)})})}a.$inject=c}}else M(a)? -(b=a.length-1,Sa(a[b],"fn"),c=a.slice(0,b)):Sa(a,"fn",!0);return c};var Pd=O("$animate"),Ye=function(){this.$get=E},Ze=function(){var a=new Ua,b=[];this.$get=["$$AnimateRunner","$rootScope",function(d,c){function e(a,b,c){var d=!1;b&&(b=y(b)?b.split(" "):M(b)?b:[],q(b,function(b){b&&(d=!0,a[b]=c)}));return d}function f(){q(b,function(b){var c=a.get(b);if(c){var d=Wf(b.attr("class")),e="",f="";q(c,function(a,b){a!==!!d[b]&&(a?e+=(e.length?" ":"")+b:f+=(f.length?" ":"")+b)});q(b,function(a){e&&Cb(a, -e);f&&Bb(a,f)});a.remove(b)}});b.length=0}return{enabled:E,on:E,off:E,pin:E,push:function(g,h,k,l){l&&l();k=k||{};k.from&&g.css(k.from);k.to&&g.css(k.to);if(k.addClass||k.removeClass)if(h=k.addClass,l=k.removeClass,k=a.get(g)||{},h=e(k,h,!0),l=e(k,l,!1),h||l)a.put(g,k),b.push(g),1===b.length&&c.$$postDigest(f);g=new d;g.complete();return g}}}]},We=["$provide",function(a){var b=this;this.$$registeredAnimations=Object.create(null);this.register=function(d,c){if(d&&"."!==d.charAt(0))throw Pd("notcsel", -d);var e=d+"-animation";b.$$registeredAnimations[d.substr(1)]=e;a.factory(e,c)};this.classNameFilter=function(a){if(1===arguments.length&&(this.$$classNameFilter=a instanceof RegExp?a:null)&&/(\s+|\/)ng-animate(\s+|\/)/.test(this.$$classNameFilter.toString()))throw Pd("nongcls","ng-animate");return this.$$classNameFilter};this.$get=["$$animateQueue",function(a){function b(a,c,d){if(d){var h;a:{for(h=0;h <= >= && || ! = |".split(" "),function(a){Nb[a]=!0});var Fg={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},gc=function(a){this.options=a};gc.prototype={constructor:gc,lex:function(a){this.text=a;this.index=0;for(this.tokens= -[];this.index=a&&"string"===typeof a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdent:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isExpOperator:function(a){return"-"=== -a||"+"===a||this.isNumber(a)},throwError:function(a,b,d){d=d||this.index;b=A(b)?"s "+b+"-"+this.index+" ["+this.text.substring(b,d)+"]":" "+d;throw ca("lexerr",a,b,this.text);},readNumber:function(){for(var a="",b=this.index;this.index","<=",">=");)a={type:s.BinaryExpression, -operator:b.text,left:a,right:this.additive()};return a},additive:function(){for(var a=this.multiplicative(),b;b=this.expect("+","-");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.multiplicative()};return a},multiplicative:function(){for(var a=this.unary(),b;b=this.expect("*","/","%");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.unary()};return a},unary:function(){var a;return(a=this.expect("+","-","!"))?{type:s.UnaryExpression,operator:a.text,prefix:!0,argument:this.unary()}: -this.primary()},primary:function(){var a;this.expect("(")?(a=this.filterChain(),this.consume(")")):this.expect("[")?a=this.arrayDeclaration():this.expect("{")?a=this.object():this.selfReferential.hasOwnProperty(this.peek().text)?a=pa(this.selfReferential[this.consume().text]):this.options.literals.hasOwnProperty(this.peek().text)?a={type:s.Literal,value:this.options.literals[this.consume().text]}:this.peek().identifier?a=this.identifier():this.peek().constant?a=this.constant():this.throwError("not a primary expression", -this.peek());for(var b;b=this.expect("(","[",".");)"("===b.text?(a={type:s.CallExpression,callee:a,arguments:this.parseArguments()},this.consume(")")):"["===b.text?(a={type:s.MemberExpression,object:a,property:this.expression(),computed:!0},this.consume("]")):"."===b.text?a={type:s.MemberExpression,object:a,property:this.identifier(),computed:!1}:this.throwError("IMPOSSIBLE");return a},filter:function(a){a=[a];for(var b={type:s.CallExpression,callee:this.identifier(),arguments:a,filter:!0};this.expect(":");)a.push(this.expression()); -return b},parseArguments:function(){var a=[];if(")"!==this.peekToken().text){do a.push(this.expression());while(this.expect(","))}return a},identifier:function(){var a=this.consume();a.identifier||this.throwError("is not a valid identifier",a);return{type:s.Identifier,name:a.text}},constant:function(){return{type:s.Literal,value:this.consume().value}},arrayDeclaration:function(){var a=[];if("]"!==this.peekToken().text){do{if(this.peek("]"))break;a.push(this.expression())}while(this.expect(","))}this.consume("]"); -return{type:s.ArrayExpression,elements:a}},object:function(){var a=[],b;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;b={type:s.Property,kind:"init"};this.peek().constant?b.key=this.constant():this.peek().identifier?b.key=this.identifier():this.throwError("invalid key",this.peek());this.consume(":");b.value=this.expression();a.push(b)}while(this.expect(","))}this.consume("}");return{type:s.ObjectExpression,properties:a}},throwError:function(a,b){throw ca("syntax",b.text,a,b.index+1,this.text, -this.text.substring(b.index));},consume:function(a){if(0===this.tokens.length)throw ca("ueoe",this.text);var b=this.expect(a);b||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return b},peekToken:function(){if(0===this.tokens.length)throw ca("ueoe",this.text);return this.tokens[0]},peek:function(a,b,d,c){return this.peekAhead(0,a,b,d,c)},peekAhead:function(a,b,d,c,e){if(this.tokens.length>a){a=this.tokens[a];var f=a.text;if(f===b||f===d||f===c||f===e||!(b||d||c||e))return a}return!1}, -expect:function(a,b,d,c){return(a=this.peek(a,b,d,c))?(this.tokens.shift(),a):!1},selfReferential:{"this":{type:s.ThisExpression},$locals:{type:s.LocalsExpression}}};rd.prototype={compile:function(a,b){var d=this,c=this.astBuilder.ast(a);this.state={nextId:0,filters:{},expensiveChecks:b,fn:{vars:[],body:[],own:{}},assign:{vars:[],body:[],own:{}},inputs:[]};aa(c,d.$filter);var e="",f;this.stage="assign";if(f=pd(c))this.state.computing="assign",e=this.nextId(),this.recurse(f,e),this.return_(e),e="fn.assign="+ -this.generateFunction("assign","s,v,l");f=nd(c.body);d.stage="inputs";q(f,function(a,b){var c="fn"+b;d.state[c]={vars:[],body:[],own:{}};d.state.computing=c;var e=d.nextId();d.recurse(a,e);d.return_(e);d.state.inputs.push(c);a.watchId=b});this.state.computing="fn";this.stage="main";this.recurse(c);e='"'+this.USE+" "+this.STRICT+'";\n'+this.filterPrefix()+"var fn="+this.generateFunction("fn","s,l,a,i")+e+this.watchFns()+"return fn;";e=(new Function("$filter","ensureSafeMemberName","ensureSafeObject", -"ensureSafeFunction","getStringValue","ensureSafeAssignContext","ifDefined","plus","text",e))(this.$filter,Wa,ta,ld,eg,Hb,ig,md,a);this.state=this.stage=u;e.literal=qd(c);e.constant=c.constant;return e},USE:"use",STRICT:"strict",watchFns:function(){var a=[],b=this.state.inputs,d=this;q(b,function(b){a.push("var "+b+"="+d.generateFunction(b,"s"))});b.length&&a.push("fn.inputs=["+b.join(",")+"];");return a.join("")},generateFunction:function(a,b){return"function("+b+"){"+this.varsPrefix(a)+this.body(a)+ -"};"},filterPrefix:function(){var a=[],b=this;q(this.state.filters,function(d,c){a.push(d+"=$filter("+b.escape(c)+")")});return a.length?"var "+a.join(",")+";":""},varsPrefix:function(a){return this.state[a].vars.length?"var "+this.state[a].vars.join(",")+";":""},body:function(a){return this.state[a].body.join("")},recurse:function(a,b,d,c,e,f){var g,h,k=this,l,m;c=c||E;if(!f&&A(a.watchId))b=b||this.nextId(),this.if_("i",this.lazyAssign(b,this.computedMember("i",a.watchId)),this.lazyRecurse(a,b,d, -c,e,!0));else switch(a.type){case s.Program:q(a.body,function(b,c){k.recurse(b.expression,u,u,function(a){h=a});c!==a.body.length-1?k.current().body.push(h,";"):k.return_(h)});break;case s.Literal:m=this.escape(a.value);this.assign(b,m);c(m);break;case s.UnaryExpression:this.recurse(a.argument,u,u,function(a){h=a});m=a.operator+"("+this.ifDefined(h,0)+")";this.assign(b,m);c(m);break;case s.BinaryExpression:this.recurse(a.left,u,u,function(a){g=a});this.recurse(a.right,u,u,function(a){h=a});m="+"=== -a.operator?this.plus(g,h):"-"===a.operator?this.ifDefined(g,0)+a.operator+this.ifDefined(h,0):"("+g+")"+a.operator+"("+h+")";this.assign(b,m);c(m);break;case s.LogicalExpression:b=b||this.nextId();k.recurse(a.left,b);k.if_("&&"===a.operator?b:k.not(b),k.lazyRecurse(a.right,b));c(b);break;case s.ConditionalExpression:b=b||this.nextId();k.recurse(a.test,b);k.if_(b,k.lazyRecurse(a.alternate,b),k.lazyRecurse(a.consequent,b));c(b);break;case s.Identifier:b=b||this.nextId();d&&(d.context="inputs"===k.stage? -"s":this.assign(this.nextId(),this.getHasOwnProperty("l",a.name)+"?l:s"),d.computed=!1,d.name=a.name);Wa(a.name);k.if_("inputs"===k.stage||k.not(k.getHasOwnProperty("l",a.name)),function(){k.if_("inputs"===k.stage||"s",function(){e&&1!==e&&k.if_(k.not(k.nonComputedMember("s",a.name)),k.lazyAssign(k.nonComputedMember("s",a.name),"{}"));k.assign(b,k.nonComputedMember("s",a.name))})},b&&k.lazyAssign(b,k.nonComputedMember("l",a.name)));(k.state.expensiveChecks||Ib(a.name))&&k.addEnsureSafeObject(b);c(b); -break;case s.MemberExpression:g=d&&(d.context=this.nextId())||this.nextId();b=b||this.nextId();k.recurse(a.object,g,u,function(){k.if_(k.notNull(g),function(){e&&1!==e&&k.addEnsureSafeAssignContext(g);if(a.computed)h=k.nextId(),k.recurse(a.property,h),k.getStringValue(h),k.addEnsureSafeMemberName(h),e&&1!==e&&k.if_(k.not(k.computedMember(g,h)),k.lazyAssign(k.computedMember(g,h),"{}")),m=k.ensureSafeObject(k.computedMember(g,h)),k.assign(b,m),d&&(d.computed=!0,d.name=h);else{Wa(a.property.name);e&& -1!==e&&k.if_(k.not(k.nonComputedMember(g,a.property.name)),k.lazyAssign(k.nonComputedMember(g,a.property.name),"{}"));m=k.nonComputedMember(g,a.property.name);if(k.state.expensiveChecks||Ib(a.property.name))m=k.ensureSafeObject(m);k.assign(b,m);d&&(d.computed=!1,d.name=a.property.name)}},function(){k.assign(b,"undefined")});c(b)},!!e);break;case s.CallExpression:b=b||this.nextId();a.filter?(h=k.filter(a.callee.name),l=[],q(a.arguments,function(a){var b=k.nextId();k.recurse(a,b);l.push(b)}),m=h+"("+ -l.join(",")+")",k.assign(b,m),c(b)):(h=k.nextId(),g={},l=[],k.recurse(a.callee,h,g,function(){k.if_(k.notNull(h),function(){k.addEnsureSafeFunction(h);q(a.arguments,function(a){k.recurse(a,k.nextId(),u,function(a){l.push(k.ensureSafeObject(a))})});g.name?(k.state.expensiveChecks||k.addEnsureSafeObject(g.context),m=k.member(g.context,g.name,g.computed)+"("+l.join(",")+")"):m=h+"("+l.join(",")+")";m=k.ensureSafeObject(m);k.assign(b,m)},function(){k.assign(b,"undefined")});c(b)}));break;case s.AssignmentExpression:h= -this.nextId();g={};if(!od(a.left))throw ca("lval");this.recurse(a.left,u,g,function(){k.if_(k.notNull(g.context),function(){k.recurse(a.right,h);k.addEnsureSafeObject(k.member(g.context,g.name,g.computed));k.addEnsureSafeAssignContext(g.context);m=k.member(g.context,g.name,g.computed)+a.operator+h;k.assign(b,m);c(b||m)})},1);break;case s.ArrayExpression:l=[];q(a.elements,function(a){k.recurse(a,k.nextId(),u,function(a){l.push(a)})});m="["+l.join(",")+"]";this.assign(b,m);c(m);break;case s.ObjectExpression:l= -[];q(a.properties,function(a){k.recurse(a.value,k.nextId(),u,function(b){l.push(k.escape(a.key.type===s.Identifier?a.key.name:""+a.key.value)+":"+b)})});m="{"+l.join(",")+"}";this.assign(b,m);c(m);break;case s.ThisExpression:this.assign(b,"s");c("s");break;case s.LocalsExpression:this.assign(b,"l");c("l");break;case s.NGValueParameter:this.assign(b,"v"),c("v")}},getHasOwnProperty:function(a,b){var d=a+"."+b,c=this.current().own;c.hasOwnProperty(d)||(c[d]=this.nextId(!1,a+"&&("+this.escape(b)+" in "+ -a+")"));return c[d]},assign:function(a,b){if(a)return this.current().body.push(a,"=",b,";"),a},filter:function(a){this.state.filters.hasOwnProperty(a)||(this.state.filters[a]=this.nextId(!0));return this.state.filters[a]},ifDefined:function(a,b){return"ifDefined("+a+","+this.escape(b)+")"},plus:function(a,b){return"plus("+a+","+b+")"},return_:function(a){this.current().body.push("return ",a,";")},if_:function(a,b,d){if(!0===a)b();else{var c=this.current().body;c.push("if(",a,"){");b();c.push("}"); -d&&(c.push("else{"),d(),c.push("}"))}},not:function(a){return"!("+a+")"},notNull:function(a){return a+"!=null"},nonComputedMember:function(a,b){return a+"."+b},computedMember:function(a,b){return a+"["+b+"]"},member:function(a,b,d){return d?this.computedMember(a,b):this.nonComputedMember(a,b)},addEnsureSafeObject:function(a){this.current().body.push(this.ensureSafeObject(a),";")},addEnsureSafeMemberName:function(a){this.current().body.push(this.ensureSafeMemberName(a),";")},addEnsureSafeFunction:function(a){this.current().body.push(this.ensureSafeFunction(a), -";")},addEnsureSafeAssignContext:function(a){this.current().body.push(this.ensureSafeAssignContext(a),";")},ensureSafeObject:function(a){return"ensureSafeObject("+a+",text)"},ensureSafeMemberName:function(a){return"ensureSafeMemberName("+a+",text)"},ensureSafeFunction:function(a){return"ensureSafeFunction("+a+",text)"},getStringValue:function(a){this.assign(a,"getStringValue("+a+")")},ensureSafeAssignContext:function(a){return"ensureSafeAssignContext("+a+",text)"},lazyRecurse:function(a,b,d,c,e,f){var g= -this;return function(){g.recurse(a,b,d,c,e,f)}},lazyAssign:function(a,b){var d=this;return function(){d.assign(a,b)}},stringEscapeRegex:/[^ a-zA-Z0-9]/g,stringEscapeFn:function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)},escape:function(a){if(y(a))return"'"+a.replace(this.stringEscapeRegex,this.stringEscapeFn)+"'";if(R(a))return a.toString();if(!0===a)return"true";if(!1===a)return"false";if(null===a)return"null";if("undefined"===typeof a)return"undefined";throw ca("esc");},nextId:function(a, -b){var d="v"+this.state.nextId++;a||this.current().vars.push(d+(b?"="+b:""));return d},current:function(){return this.state[this.state.computing]}};sd.prototype={compile:function(a,b){var d=this,c=this.astBuilder.ast(a);this.expression=a;this.expensiveChecks=b;aa(c,d.$filter);var e,f;if(e=pd(c))f=this.recurse(e);e=nd(c.body);var g;e&&(g=[],q(e,function(a,b){var c=d.recurse(a);a.input=c;g.push(c);a.watchId=b}));var h=[];q(c.body,function(a){h.push(d.recurse(a.expression))});e=0===c.body.length?E:1=== -c.body.length?h[0]:function(a,b){var c;q(h,function(d){c=d(a,b)});return c};f&&(e.assign=function(a,b,c){return f(a,c,b)});g&&(e.inputs=g);e.literal=qd(c);e.constant=c.constant;return e},recurse:function(a,b,d){var c,e,f=this,g;if(a.input)return this.inputs(a.input,a.watchId);switch(a.type){case s.Literal:return this.value(a.value,b);case s.UnaryExpression:return e=this.recurse(a.argument),this["unary"+a.operator](e,b);case s.BinaryExpression:return c=this.recurse(a.left),e=this.recurse(a.right), -this["binary"+a.operator](c,e,b);case s.LogicalExpression:return c=this.recurse(a.left),e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case s.ConditionalExpression:return this["ternary?:"](this.recurse(a.test),this.recurse(a.alternate),this.recurse(a.consequent),b);case s.Identifier:return Wa(a.name,f.expression),f.identifier(a.name,f.expensiveChecks||Ib(a.name),b,d,f.expression);case s.MemberExpression:return c=this.recurse(a.object,!1,!!d),a.computed||(Wa(a.property.name,f.expression), -e=a.property.name),a.computed&&(e=this.recurse(a.property)),a.computed?this.computedMember(c,e,b,d,f.expression):this.nonComputedMember(c,e,f.expensiveChecks,b,d,f.expression);case s.CallExpression:return g=[],q(a.arguments,function(a){g.push(f.recurse(a))}),a.filter&&(e=this.$filter(a.callee.name)),a.filter||(e=this.recurse(a.callee,!0)),a.filter?function(a,c,d,f){for(var n=[],p=0;p":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>b(c,e,f,g);return d?{value:c}:c}},"binary<=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f, -g)<=b(c,e,f,g);return d?{value:c}:c}},"binary>=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>=b(c,e,f,g);return d?{value:c}:c}},"binary&&":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)&&b(c,e,f,g);return d?{value:c}:c}},"binary||":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)||b(c,e,f,g);return d?{value:c}:c}},"ternary?:":function(a,b,d,c){return function(e,f,g,h){e=a(e,f,g,h)?b(e,f,g,h):d(e,f,g,h);return c?{value:e}:e}},value:function(a,b){return function(){return b?{context:u, -name:u,value:a}:a}},identifier:function(a,b,d,c,e){return function(f,g,h,k){f=g&&a in g?g:f;c&&1!==c&&f&&!f[a]&&(f[a]={});g=f?f[a]:u;b&&ta(g,e);return d?{context:f,name:a,value:g}:g}},computedMember:function(a,b,d,c,e){return function(f,g,h,k){var l=a(f,g,h,k),m,n;null!=l&&(m=b(f,g,h,k),m+="",Wa(m,e),c&&1!==c&&(Hb(l),l&&!l[m]&&(l[m]={})),n=l[m],ta(n,e));return d?{context:l,name:m,value:n}:n}},nonComputedMember:function(a,b,d,c,e,f){return function(g,h,k,l){g=a(g,h,k,l);e&&1!==e&&(Hb(g),g&&!g[b]&& -(g[b]={}));h=null!=g?g[b]:u;(d||Ib(b))&&ta(h,f);return c?{context:g,name:b,value:h}:h}},inputs:function(a,b){return function(d,c,e,f){return f?f[b]:a(d,c,e)}}};var hc=function(a,b,d){this.lexer=a;this.$filter=b;this.options=d;this.ast=new s(a,d);this.astCompiler=d.csp?new sd(this.ast,b):new rd(this.ast,b)};hc.prototype={constructor:hc,parse:function(a){return this.astCompiler.compile(a,this.options.expensiveChecks)}};var jg=Object.prototype.valueOf,ua=O("$sce"),ma={HTML:"html",CSS:"css",URL:"url", -RESOURCE_URL:"resourceUrl",JS:"js"},lg=O("$compile"),Z=P.createElement("a"),wd=sa(T.location.href);xd.$inject=["$document"];Jc.$inject=["$provide"];var Ed=22,Dd=".",jc="0";yd.$inject=["$locale"];Ad.$inject=["$locale"];var wg={yyyy:X("FullYear",4,0,!1,!0),yy:X("FullYear",2,0,!0,!0),y:X("FullYear",1,0,!1,!0),MMMM:lb("Month"),MMM:lb("Month",!0),MM:X("Month",2,1),M:X("Month",1,1),LLLL:lb("Month",!1,!0),dd:X("Date",2),d:X("Date",1),HH:X("Hours",2),H:X("Hours",1),hh:X("Hours",2,-12),h:X("Hours",1,-12), -mm:X("Minutes",2),m:X("Minutes",1),ss:X("Seconds",2),s:X("Seconds",1),sss:X("Milliseconds",3),EEEE:lb("Day"),EEE:lb("Day",!0),a:function(a,b){return 12>a.getHours()?b.AMPMS[0]:b.AMPMS[1]},Z:function(a,b,d){a=-1*d;return a=(0<=a?"+":"")+(Jb(Math[0=a.getFullYear()?b.ERANAMES[0]:b.ERANAMES[1]}},vg=/((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, -ug=/^\-?\d+$/;zd.$inject=["$locale"];var pg=da(N),qg=da(vb);Bd.$inject=["$parse"];var me=da({restrict:"E",compile:function(a,b){if(!b.href&&!b.xlinkHref)return function(a,b){if("a"===b[0].nodeName.toLowerCase()){var e="[object SVGAnimatedString]"===ka.call(b.prop("href"))?"xlink:href":"href";b.on("click",function(a){b.attr(e)||a.preventDefault()})}}}}),wb={};q(Eb,function(a,b){function d(a,d,e){a.$watch(e[c],function(a){e.$set(b,!!a)})}if("multiple"!=a){var c=ya("ng-"+b),e=d;"checked"===a&&(e=function(a, -b,e){e.ngModel!==e[c]&&d(a,b,e)});wb[c]=function(){return{restrict:"A",priority:100,link:e}}}});q($c,function(a,b){wb[b]=function(){return{priority:100,link:function(a,c,e){if("ngPattern"===b&&"/"==e.ngPattern.charAt(0)&&(c=e.ngPattern.match(yg))){e.$set("ngPattern",new RegExp(c[1],c[2]));return}a.$watch(e[b],function(a){e.$set(b,a)})}}}});q(["src","srcset","href"],function(a){var b=ya("ng-"+a);wb[b]=function(){return{priority:99,link:function(d,c,e){var f=a,g=a;"href"===a&&"[object SVGAnimatedString]"=== -ka.call(c.prop("href"))&&(g="xlinkHref",e.$attr[g]="xlink:href",f=null);e.$observe(b,function(b){b?(e.$set(g,b),Da&&f&&c.prop(f,e[g])):"href"===a&&e.$set(g,null)})}}}});var Kb={$addControl:E,$$renameControl:function(a,b){a.$name=b},$removeControl:E,$setValidity:E,$setDirty:E,$setPristine:E,$setSubmitted:E};Hd.$inject=["$element","$attrs","$scope","$animate","$interpolate"];var Qd=function(a){return["$timeout","$parse",function(b,d){function c(a){return""===a?d('this[""]').assign:d(a).assign||E}return{name:"form", -restrict:a?"EAC":"E",require:["form","^^?form"],controller:Hd,compile:function(d,f){d.addClass(Xa).addClass(pb);var g=f.name?"name":a&&f.ngForm?"ngForm":!1;return{pre:function(a,d,e,f){var n=f[0];if(!("action"in e)){var p=function(b){a.$apply(function(){n.$commitViewValue();n.$setSubmitted()});b.preventDefault()};d[0].addEventListener("submit",p,!1);d.on("$destroy",function(){b(function(){d[0].removeEventListener("submit",p,!1)},0,!1)})}(f[1]||n.$$parentForm).$addControl(n);var q=g?c(n.$name):E;g&& -(q(a,n),e.$observe(g,function(b){n.$name!==b&&(q(a,u),n.$$parentForm.$$renameControl(n,b),q=c(n.$name),q(a,n))}));d.on("$destroy",function(){n.$$parentForm.$removeControl(n);q(a,u);S(n,Kb)})}}}}}]},ne=Qd(),Ae=Qd(!0),xg=/^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/,Gg=/^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+\])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i,Hg=/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i, -Ig=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/,Rd=/^(\d{4,})-(\d{2})-(\d{2})$/,Sd=/^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,nc=/^(\d{4,})-W(\d\d)$/,Td=/^(\d{4,})-(\d\d)$/,Ud=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Jd=V();q(["date","datetime-local","month","time","week"],function(a){Jd[a]=!0});var Vd={text:function(a,b,d,c,e,f){mb(a,b,d,c,e,f);lc(c)},date:nb("date",Rd,Mb(Rd,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":nb("datetimelocal",Sd,Mb(Sd,"yyyy MM dd HH mm ss sss".split(" ")), -"yyyy-MM-ddTHH:mm:ss.sss"),time:nb("time",Ud,Mb(Ud,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:nb("week",nc,function(a,b){if(fa(a))return a;if(y(a)){nc.lastIndex=0;var d=nc.exec(a);if(d){var c=+d[1],e=+d[2],f=d=0,g=0,h=0,k=Fd(c),e=7*(e-1);b&&(d=b.getHours(),f=b.getMinutes(),g=b.getSeconds(),h=b.getMilliseconds());return new Date(c,0,k.getDate()+e,d,f,g,h)}}return NaN},"yyyy-Www"),month:nb("month",Td,Mb(Td,["yyyy","MM"]),"yyyy-MM"),number:function(a,b,d,c,e,f){Kd(a,b,d,c);mb(a,b,d,c,e,f);c.$$parserName= -"number";c.$parsers.push(function(a){return c.$isEmpty(a)?null:Ig.test(a)?parseFloat(a):u});c.$formatters.push(function(a){if(!c.$isEmpty(a)){if(!R(a))throw ob("numfmt",a);a=a.toString()}return a});if(A(d.min)||d.ngMin){var g;c.$validators.min=function(a){return c.$isEmpty(a)||z(g)||a>=g};d.$observe("min",function(a){A(a)&&!R(a)&&(a=parseFloat(a,10));g=R(a)&&!isNaN(a)?a:u;c.$validate()})}if(A(d.max)||d.ngMax){var h;c.$validators.max=function(a){return c.$isEmpty(a)||z(h)||a<=h};d.$observe("max",function(a){A(a)&& -!R(a)&&(a=parseFloat(a,10));h=R(a)&&!isNaN(a)?a:u;c.$validate()})}},url:function(a,b,d,c,e,f){mb(a,b,d,c,e,f);lc(c);c.$$parserName="url";c.$validators.url=function(a,b){var d=a||b;return c.$isEmpty(d)||Gg.test(d)}},email:function(a,b,d,c,e,f){mb(a,b,d,c,e,f);lc(c);c.$$parserName="email";c.$validators.email=function(a,b){var d=a||b;return c.$isEmpty(d)||Hg.test(d)}},radio:function(a,b,d,c){z(d.name)&&b.attr("name",++qb);b.on("click",function(a){b[0].checked&&c.$setViewValue(d.value,a&&a.type)});c.$render= -function(){b[0].checked=d.value==c.$viewValue};d.$observe("value",c.$render)},checkbox:function(a,b,d,c,e,f,g,h){var k=Ld(h,a,"ngTrueValue",d.ngTrueValue,!0),l=Ld(h,a,"ngFalseValue",d.ngFalseValue,!1);b.on("click",function(a){c.$setViewValue(b[0].checked,a&&a.type)});c.$render=function(){b[0].checked=c.$viewValue};c.$isEmpty=function(a){return!1===a};c.$formatters.push(function(a){return na(a,k)});c.$parsers.push(function(a){return a?k:l})},hidden:E,button:E,submit:E,reset:E,file:E},Dc=["$browser", -"$sniffer","$filter","$parse",function(a,b,d,c){return{restrict:"E",require:["?ngModel"],link:{pre:function(e,f,g,h){h[0]&&(Vd[N(g.type)]||Vd.text)(e,f,g,h[0],b,a,d,c)}}}}],Jg=/^(true|false|\d+)$/,Se=function(){return{restrict:"A",priority:100,compile:function(a,b){return Jg.test(b.ngValue)?function(a,b,e){e.$set("value",a.$eval(e.ngValue))}:function(a,b,e){a.$watch(e.ngValue,function(a){e.$set("value",a)})}}}},se=["$compile",function(a){return{restrict:"AC",compile:function(b){a.$$addBindingClass(b); -return function(b,c,e){a.$$addBindingInfo(c,e.ngBind);c=c[0];b.$watch(e.ngBind,function(a){c.textContent=z(a)?"":a})}}}}],ue=["$interpolate","$compile",function(a,b){return{compile:function(d){b.$$addBindingClass(d);return function(c,d,f){c=a(d.attr(f.$attr.ngBindTemplate));b.$$addBindingInfo(d,c.expressions);d=d[0];f.$observe("ngBindTemplate",function(a){d.textContent=z(a)?"":a})}}}}],te=["$sce","$parse","$compile",function(a,b,d){return{restrict:"A",compile:function(c,e){var f=b(e.ngBindHtml),g= -b(e.ngBindHtml,function(a){return(a||"").toString()});d.$$addBindingClass(c);return function(b,c,e){d.$$addBindingInfo(c,e.ngBindHtml);b.$watch(g,function(){c.html(a.getTrustedHtml(f(b))||"")})}}}}],Re=da({restrict:"A",require:"ngModel",link:function(a,b,d,c){c.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),ve=mc("",!0),xe=mc("Odd",0),we=mc("Even",1),ye=Na({compile:function(a,b){b.$set("ngCloak",u);a.removeClass("ng-cloak")}}),ze=[function(){return{restrict:"A",scope:!0,controller:"@", -priority:500}}],Ic={},Kg={blur:!0,focus:!0};q("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var b=ya("ng-"+a);Ic[b]=["$parse","$rootScope",function(d,c){return{restrict:"A",compile:function(e,f){var g=d(f[b],null,!0);return function(b,d){d.on(a,function(d){var e=function(){g(b,{$event:d})};Kg[a]&&c.$$phase?b.$evalAsync(e):b.$apply(e)})}}}}]});var Ce=["$animate","$compile",function(a, -b){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(d,c,e,f,g){var h,k,l;d.$watch(e.ngIf,function(d){d?k||g(function(d,f){k=f;d[d.length++]=b.$$createComment("end ngIf",e.ngIf);h={clone:d};a.enter(d,c.parent(),c)}):(l&&(l.remove(),l=null),k&&(k.$destroy(),k=null),h&&(l=ub(h.clone),a.leave(l).then(function(){l=null}),h=null))})}}}],De=["$templateRequest","$anchorScroll","$animate",function(a,b,d){return{restrict:"ECA",priority:400,terminal:!0, -transclude:"element",controller:ea.noop,compile:function(c,e){var f=e.ngInclude||e.src,g=e.onload||"",h=e.autoscroll;return function(c,e,m,n,p){var q=0,s,x,r,w=function(){x&&(x.remove(),x=null);s&&(s.$destroy(),s=null);r&&(d.leave(r).then(function(){x=null}),x=r,r=null)};c.$watch(f,function(f){var m=function(){!A(h)||h&&!c.$eval(h)||b()},t=++q;f?(a(f,!0).then(function(a){if(!c.$$destroyed&&t===q){var b=c.$new();n.template=a;a=p(b,function(a){w();d.enter(a,null,e).then(m)});s=b;r=a;s.$emit("$includeContentLoaded", -f);c.$eval(g)}},function(){c.$$destroyed||t!==q||(w(),c.$emit("$includeContentError",f))}),c.$emit("$includeContentRequested",f)):(w(),n.template=null)})}}}}],Ue=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(b,d,c,e){ka.call(d[0]).match(/SVG/)?(d.empty(),a(Lc(e.template,P).childNodes)(b,function(a){d.append(a)},{futureParentElement:d})):(d.html(e.template),a(d.contents())(b))}}}],Ee=Na({priority:450,compile:function(){return{pre:function(a,b,d){a.$eval(d.ngInit)}}}}), -Qe=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,b,d,c){var e=b.attr(d.$attr.ngList)||", ",f="false"!==d.ngTrim,g=f?W(e):e;c.$parsers.push(function(a){if(!z(a)){var b=[];a&&q(a.split(g),function(a){a&&b.push(f?W(a):a)});return b}});c.$formatters.push(function(a){return M(a)?a.join(e):u});c.$isEmpty=function(a){return!a||!a.length}}}},pb="ng-valid",Md="ng-invalid",Xa="ng-pristine",Lb="ng-dirty",Od="ng-pending",ob=O("ngModel"),Lg=["$scope","$exceptionHandler","$attrs", -"$element","$parse","$animate","$timeout","$rootScope","$q","$interpolate",function(a,b,d,c,e,f,g,h,k,l){this.$modelValue=this.$viewValue=Number.NaN;this.$$rawModelValue=u;this.$validators={};this.$asyncValidators={};this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$untouched=!0;this.$touched=!1;this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$error={};this.$$success={};this.$pending=u;this.$name=l(d.name||"",!1)(a);this.$$parentForm=Kb;var m=e(d.ngModel), -n=m.assign,p=m,s=n,y=null,x,r=this;this.$$setOptions=function(a){if((r.$options=a)&&a.getterSetter){var b=e(d.ngModel+"()"),f=e(d.ngModel+"($$$p)");p=function(a){var c=m(a);D(c)&&(c=b(a));return c};s=function(a,b){D(m(a))?f(a,{$$$p:b}):n(a,b)}}else if(!m.assign)throw ob("nonassign",d.ngModel,wa(c));};this.$render=E;this.$isEmpty=function(a){return z(a)||""===a||null===a||a!==a};this.$$updateEmptyClasses=function(a){r.$isEmpty(a)?(f.removeClass(c,"ng-not-empty"),f.addClass(c,"ng-empty")):(f.removeClass(c, -"ng-empty"),f.addClass(c,"ng-not-empty"))};var w=0;Id({ctrl:this,$element:c,set:function(a,b){a[b]=!0},unset:function(a,b){delete a[b]},$animate:f});this.$setPristine=function(){r.$dirty=!1;r.$pristine=!0;f.removeClass(c,Lb);f.addClass(c,Xa)};this.$setDirty=function(){r.$dirty=!0;r.$pristine=!1;f.removeClass(c,Xa);f.addClass(c,Lb);r.$$parentForm.$setDirty()};this.$setUntouched=function(){r.$touched=!1;r.$untouched=!0;f.setClass(c,"ng-untouched","ng-touched")};this.$setTouched=function(){r.$touched= -!0;r.$untouched=!1;f.setClass(c,"ng-touched","ng-untouched")};this.$rollbackViewValue=function(){g.cancel(y);r.$viewValue=r.$$lastCommittedViewValue;r.$render()};this.$validate=function(){if(!R(r.$modelValue)||!isNaN(r.$modelValue)){var a=r.$$rawModelValue,b=r.$valid,c=r.$modelValue,d=r.$options&&r.$options.allowInvalid;r.$$runValidators(a,r.$$lastCommittedViewValue,function(e){d||b===e||(r.$modelValue=e?a:u,r.$modelValue!==c&&r.$$writeModelToScope())})}};this.$$runValidators=function(a,b,c){function d(){var c= -!0;q(r.$validators,function(d,e){var g=d(a,b);c=c&&g;f(e,g)});return c?!0:(q(r.$asyncValidators,function(a,b){f(b,null)}),!1)}function e(){var c=[],d=!0;q(r.$asyncValidators,function(e,g){var h=e(a,b);if(!h||!D(h.then))throw ob("nopromise",h);f(g,u);c.push(h.then(function(){f(g,!0)},function(){d=!1;f(g,!1)}))});c.length?k.all(c).then(function(){g(d)},E):g(!0)}function f(a,b){h===w&&r.$setValidity(a,b)}function g(a){h===w&&c(a)}w++;var h=w;(function(){var a=r.$$parserName||"parse";if(z(x))f(a,null); -else return x||(q(r.$validators,function(a,b){f(b,null)}),q(r.$asyncValidators,function(a,b){f(b,null)})),f(a,x),x;return!0})()?d()?e():g(!1):g(!1)};this.$commitViewValue=function(){var a=r.$viewValue;g.cancel(y);if(r.$$lastCommittedViewValue!==a||""===a&&r.$$hasNativeValidators)r.$$updateEmptyClasses(a),r.$$lastCommittedViewValue=a,r.$pristine&&this.$setDirty(),this.$$parseAndValidate()};this.$$parseAndValidate=function(){var b=r.$$lastCommittedViewValue;if(x=z(b)?u:!0)for(var c=0;ce||c.$isEmpty(b)|| -b.length<=e}}}}},Gc=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var e=0;d.$observe("minlength",function(a){e=Y(a)||0;c.$validate()});c.$validators.minlength=function(a,b){return c.$isEmpty(b)||b.length>=e}}}}};T.angular.bootstrap?T.console&&console.log("WARNING: Tried to load angular more than once."):(he(),je(ea),ea.module("ngLocale",[],["$provide",function(a){function b(a){a+="";var b=a.indexOf(".");return-1==b?0:a.length-b-1}a.value("$locale",{DATETIME_FORMATS:{AMPMS:["AM", -"PM"],DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),ERANAMES:["Before Christ","Anno Domini"],ERAS:["BC","AD"],FIRSTDAYOFWEEK:6,MONTH:"January February March April May June July August September October November December".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),STANDALONEMONTH:"January February March April May June July August September October November December".split(" "),WEEKENDRANGE:[5, -6],fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",medium:"MMM d, y h:mm:ss a",mediumDate:"MMM d, y",mediumTime:"h:mm:ss a","short":"M/d/yy h:mm a",shortDate:"M/d/yy",shortTime:"h:mm a"},NUMBER_FORMATS:{CURRENCY_SYM:"$",DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{gSize:3,lgSize:3,maxFrac:3,minFrac:0,minInt:1,negPre:"-",negSuf:"",posPre:"",posSuf:""},{gSize:3,lgSize:3,maxFrac:2,minFrac:2,minInt:1,negPre:"-\u00a4",negSuf:"",posPre:"\u00a4",posSuf:""}]},id:"en-us",localeID:"en_US",pluralCat:function(a, -c){var e=a|0,f=c;u===f&&(f=Math.min(b(a),3));Math.pow(10,f);return 1==e&&0==f?"one":"other"}})}]),H(P).ready(function(){de(P,yc)}))})(window,document);!window.angular.$$csp().noInlineStyle&&window.angular.element(document.head).prepend(''); -//# sourceMappingURL=angular.min.js.map diff --git a/aci/e2e/aci-demo/web/static/app.js b/aci/e2e/aci-demo/web/static/app.js deleted file mode 100644 index b782295a0..000000000 --- a/aci/e2e/aci-demo/web/static/app.js +++ /dev/null @@ -1,45 +0,0 @@ -"use strict"; - -var lab = angular.module('lab', []); - -lab.controller('LabCtrl', function ($scope, $http, $timeout) { - $scope.noun1 = ""; - $scope.noun2 = ""; - $scope.adjective1 = ""; - $scope.adjective2 = ""; - $scope.verb = ""; - - getWord($http, $timeout, '/words/noun', function(resp) { - $scope.noun1 = word(resp); - }); - getWord($http, $timeout, '/words/noun', function(resp) { - $scope.noun2 = word(resp); - }); - getWord($http, $timeout, '/words/adjective', function(resp) { - var adj = word(resp); - adj.word = adj.word.charAt(0).toUpperCase() + adj.word.substr(1) - $scope.adjective1 = adj; - }); - getWord($http, $timeout, '/words/adjective', function(resp) { - $scope.adjective2 = word(resp); - }); - getWord($http, $timeout, '/words/verb', function(resp) { - $scope.verb = word(resp); - }); -}); - -function getWord($http, $timeout, url, callback) { - $http.get(url).then(callback, function(resp) { - $timeout(function() { - console.log("Retry: " + url); - getWord($http, $timeout, url, callback); - }, 500); - }); -} - -function word(resp) { - return { - word: resp.data.word, - hostname: resp.headers()["source"] - }; -} diff --git a/aci/e2e/aci-demo/web/static/favicon.ico b/aci/e2e/aci-demo/web/static/favicon.ico deleted file mode 100644 index 0d5db4da3..000000000 Binary files a/aci/e2e/aci-demo/web/static/favicon.ico and /dev/null differ diff --git a/aci/e2e/aci-demo/web/static/fonts/font1.woff2 b/aci/e2e/aci-demo/web/static/fonts/font1.woff2 deleted file mode 100644 index 544cd2939..000000000 Binary files a/aci/e2e/aci-demo/web/static/fonts/font1.woff2 and /dev/null differ diff --git a/aci/e2e/aci-demo/web/static/fonts/font2.woff2 b/aci/e2e/aci-demo/web/static/fonts/font2.woff2 deleted file mode 100644 index 77531b875..000000000 Binary files a/aci/e2e/aci-demo/web/static/fonts/font2.woff2 and /dev/null differ diff --git a/aci/e2e/aci-demo/web/static/images/dockercon-log.png b/aci/e2e/aci-demo/web/static/images/dockercon-log.png deleted file mode 100644 index 88a2ce23a..000000000 Binary files a/aci/e2e/aci-demo/web/static/images/dockercon-log.png and /dev/null differ diff --git a/aci/e2e/aci-demo/web/static/images/dockercon-logo-2020.png b/aci/e2e/aci-demo/web/static/images/dockercon-logo-2020.png deleted file mode 100644 index 836a686ba..000000000 Binary files a/aci/e2e/aci-demo/web/static/images/dockercon-logo-2020.png and /dev/null differ diff --git a/aci/e2e/aci-demo/web/static/images/homes.png b/aci/e2e/aci-demo/web/static/images/homes.png deleted file mode 100644 index aba52e0b3..000000000 Binary files a/aci/e2e/aci-demo/web/static/images/homes.png and /dev/null differ diff --git a/aci/e2e/aci-demo/web/static/images/lego_blue.png b/aci/e2e/aci-demo/web/static/images/lego_blue.png deleted file mode 100644 index acb218a6b..000000000 Binary files a/aci/e2e/aci-demo/web/static/images/lego_blue.png and /dev/null differ diff --git a/aci/e2e/aci-demo/web/static/images/lego_light_blue.png b/aci/e2e/aci-demo/web/static/images/lego_light_blue.png deleted file mode 100644 index a3bd1cc4a..000000000 Binary files a/aci/e2e/aci-demo/web/static/images/lego_light_blue.png and /dev/null differ diff --git a/aci/e2e/aci-demo/web/static/images/lego_yellow.png b/aci/e2e/aci-demo/web/static/images/lego_yellow.png deleted file mode 100644 index 81c14613d..000000000 Binary files a/aci/e2e/aci-demo/web/static/images/lego_yellow.png and /dev/null differ diff --git a/aci/e2e/aci-demo/web/static/images/logo.svg b/aci/e2e/aci-demo/web/static/images/logo.svg deleted file mode 100644 index 2a959093a..000000000 --- a/aci/e2e/aci-demo/web/static/images/logo.svg +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/aci/e2e/aci-demo/web/static/index.html b/aci/e2e/aci-demo/web/static/index.html deleted file mode 100644 index 281d455d9..000000000 --- a/aci/e2e/aci-demo/web/static/index.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - Docker Compose demo - - - - - -
-
- - - - - - - - -
-
- - - - -
-
- - - - - - - - -
-
- - - - - - - diff --git a/aci/e2e/aci-demo/web/static/style.css b/aci/e2e/aci-demo/web/static/style.css deleted file mode 100644 index 441d3fc21..000000000 --- a/aci/e2e/aci-demo/web/static/style.css +++ /dev/null @@ -1,110 +0,0 @@ -/* latin-ext */ -@font-face { - font-family: 'Raleway'; - font-style: normal; - font-weight: 400; - src: local('Raleway'), local('Raleway-Regular'), url('fonts/font1.woff2') format('woff2'); - unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; -} -/* latin */ -@font-face { - font-family: 'Raleway'; - font-style: normal; - font-weight: 400; - src: local('Raleway'), local('Raleway-Regular'), url('fonts/font2.woff2') format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215; -} - -body { - text-align: center; - margin: 0; - padding: 0; - background-color: #001f5b; -} - -.logo img{ - margin: -50px 0; - width: 435px; -} - -.footer { - position: absolute; - bottom: 0; - width: 100%; - z-index: -1; - opacity: 0.5; -} - -.footer img { - max-width: 100%; - vertical-align: middle; -} - -.sentence { - margin: 70px auto 0 auto; -} - -.line { - margin-bottom: 30px; - transform: translateX(-100%) rotate(-20deg); -} - -.slide-in { - animation: slide-in .5s forwards ease-in; -} - -.line3.slide-in { - animation: slide-in 1s forwards ease-in; -} - -.line2.slide-in { - animation: slide-in 1.2s forwards ease-in; -} - -@keyframes slide-in { - 100% { - transform: translateX(0%); - } -} - -.result { - position: relative; - display: inline-block; - padding: 0 20px; - margin: 0 10px; - color: white; - height: 175px; - width: 330px; -} - -.result .word { - display: inline-block; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 45px; - color: white; - line-height: 155px; - height: 175px; - vertical-align: middle; - margin-top: 20px; -} - -.result .hostname { - position: absolute; - width: 100%; - left: 0; - bottom: 8px; - font-size: 0.8em; - height: 14px; -} - -.noun { - background-image: url('images/lego_blue.png') !important; -} - -.verb { - background-image: url('images/lego_yellow.png') !important; -} - -.adjective { - background-image: url('images/lego_light_blue.png') !important; -} diff --git a/aci/e2e/aci-demo/words/.dockerignore b/aci/e2e/aci-demo/words/.dockerignore deleted file mode 100644 index f83e8cf07..000000000 --- a/aci/e2e/aci-demo/words/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -.idea -target -*.iml diff --git a/aci/e2e/aci-demo/words/Dockerfile b/aci/e2e/aci-demo/words/Dockerfile deleted file mode 100644 index 2b32e55d0..000000000 --- a/aci/e2e/aci-demo/words/Dockerfile +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright 2020 Docker Compose CLI authors - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# BUILD -FROM openjdk:8u171-jdk-alpine as build - -RUN MAVEN_VERSION=3.5.0 \ - && cd /usr/share \ - && wget http://archive.apache.org/dist/maven/maven-3/$MAVEN_VERSION/binaries/apache-maven-$MAVEN_VERSION-bin.tar.gz -O - | tar xzf - \ - && mv /usr/share/apache-maven-$MAVEN_VERSION /usr/share/maven \ - && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn - -WORKDIR /home/lab - -COPY pom.xml . -RUN mvn verify -DskipTests --fail-never - -COPY src ./src -RUN mvn verify - -# RUN -FROM openjdk:8u171-jre-alpine - -ENTRYPOINT ["java", "-Xmx8m", "-Xms8m", "-jar", "/app/words.jar"] -EXPOSE 8080 - -WORKDIR /app -COPY --from=build /home/lab/target . diff --git a/aci/e2e/aci-demo/words/pom.xml b/aci/e2e/aci-demo/words/pom.xml deleted file mode 100644 index fc6486673..000000000 --- a/aci/e2e/aci-demo/words/pom.xml +++ /dev/null @@ -1,99 +0,0 @@ - - - 4.0.0 - - codestory - words - 1.0-SNAPSHOT - - - UTF-8 - 1.8 - 1.8 - - - - words - - - - - maven-clean-plugin - 3.0.0 - - - maven-compiler-plugin - 3.6.1 - - - maven-deploy-plugin - 2.8.2 - - - maven-install-plugin - 2.5.2 - - - maven-resources-plugin - 3.0.2 - - - maven-site-plugin - 3.6 - - - maven-release-plugin - 2.5.3 - - - maven-surefire-plugin - 2.19.1 - - - - - - - maven-dependency-plugin - 3.0.0 - - - copy-dependencies - package - - copy-dependencies - - - - - - maven-jar-plugin - 3.0.2 - - - - true - dependency - Main - - - - - - - - - - com.google.guava - guava - [24.1.1,) - - - org.postgresql - postgresql - 42.1.4 - - - diff --git a/aci/e2e/aci-demo/words/src/main/java/Main.java b/aci/e2e/aci-demo/words/src/main/java/Main.java deleted file mode 100644 index 59bb8e60e..000000000 --- a/aci/e2e/aci-demo/words/src/main/java/Main.java +++ /dev/null @@ -1,53 +0,0 @@ -import com.google.common.base.Charsets; -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; -import com.sun.net.httpserver.HttpHandler; -import com.sun.net.httpserver.HttpServer; - -import java.io.OutputStream; -import java.net.InetSocketAddress; -import java.sql.*; -import java.util.NoSuchElementException; - -public class Main { - public static void main(String[] args) throws Exception { - Class.forName("org.postgresql.Driver"); - - HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0); - server.createContext("/noun", handler(Suppliers.memoize(() -> randomWord("nouns")))); - server.createContext("/verb", handler(Suppliers.memoize(() -> randomWord("verbs")))); - server.createContext("/adjective", handler(Suppliers.memoize(() -> randomWord("adjectives")))); - server.start(); - } - - private static String randomWord(String table) { - try (Connection connection = DriverManager.getConnection("jdbc:postgresql://db:5432/postgres", "postgres", "")) { - try (Statement statement = connection.createStatement()) { - try (ResultSet set = statement.executeQuery("SELECT word FROM " + table + " ORDER BY random() LIMIT 1")) { - while (set.next()) { - return set.getString(1); - } - } - } - } catch (SQLException e) { - e.printStackTrace(); - } - - throw new NoSuchElementException(table); - } - - private static HttpHandler handler(Supplier word) { - return t -> { - String response = "{\"word\":\"" + word.get() + "\"}"; - byte[] bytes = response.getBytes(Charsets.UTF_8); - - System.out.println(response); - t.getResponseHeaders().add("content-type", "application/json; charset=utf-8"); - t.sendResponseHeaders(200, bytes.length); - - try (OutputStream os = t.getResponseBody()) { - os.write(bytes); - } - }; - } -} diff --git a/aci/e2e/aci_secrets_resources/compose.yaml b/aci/e2e/aci_secrets_resources/compose.yaml deleted file mode 100644 index 1f486902e..000000000 --- a/aci/e2e/aci_secrets_resources/compose.yaml +++ /dev/null @@ -1,46 +0,0 @@ -services: - web1: - build: ./web - image: dockerinternal/e2e_test_secret_server - ports: - - "80:80" - secrets: - - source: mysecret1 - target: mytarget1 - - mysecret2 - deploy: - restart_policy: - condition: on-failure - resources: - limits: - cpus: '0.7' - memory: 1G - reservations: - cpus: '0.5' - memory: 0.5G - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:80/healthz"] - interval: 5s - - web2: - build: ./web - image: dockerinternal/e2e_test_secret_server - ports: - - "8080:8080" - environment: - - PORT=8080 - deploy: - restart_policy: - condition: on-failure - resources: - reservations: - cpus: '0.5' - memory: 0.7G - secrets: - - mysecret2 - -secrets: - mysecret1: - file: ./my_secret1.txt - mysecret2: - file: ./my_secret2.txt diff --git a/aci/e2e/aci_secrets_resources/my_secret1.txt b/aci/e2e/aci_secrets_resources/my_secret1.txt deleted file mode 100644 index 73a420f3f..000000000 --- a/aci/e2e/aci_secrets_resources/my_secret1.txt +++ /dev/null @@ -1 +0,0 @@ -myPassword1 diff --git a/aci/e2e/aci_secrets_resources/my_secret2.txt b/aci/e2e/aci_secrets_resources/my_secret2.txt deleted file mode 100644 index 8bf7f5754..000000000 --- a/aci/e2e/aci_secrets_resources/my_secret2.txt +++ /dev/null @@ -1 +0,0 @@ -another_password diff --git a/aci/e2e/aci_secrets_resources/web/Dockerfile b/aci/e2e/aci_secrets_resources/web/Dockerfile deleted file mode 100644 index 2bb6398e6..000000000 --- a/aci/e2e/aci_secrets_resources/web/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -# syntax=docker/dockerfile:experimental - - -# Copyright 2020 Docker Compose CLI authors - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -FROM golang:1.16-alpine AS build -COPY main.go . -RUN --mount=type=cache,target=/go/pkg/mod \ - go build -trimpath -ldflags="-s -w" -o server main.go - -FROM alpine -RUN apk --no-cache add curl -COPY --from=build /go/server / -CMD /server "${PORT:-80}" "${DIR:-/run/secrets}" diff --git a/aci/e2e/aci_secrets_resources/web/main.go b/aci/e2e/aci_secrets_resources/web/main.go deleted file mode 100644 index 2dc9fe85b..000000000 --- a/aci/e2e/aci_secrets_resources/web/main.go +++ /dev/null @@ -1,65 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package main - -import ( - "fmt" - "net/http" - "os" -) - -func main() { - if len(os.Args) < 2 { - fmt.Fprintln(os.Stderr, "Usage: web PORT FOLDER") - os.Exit(1) - } - - http.HandleFunc("/failtestserver", log(fail)) - http.HandleFunc("/healthz", log(healthz)) - dir := os.Args[2] - fileServer := http.FileServer(http.Dir(dir)) - http.HandleFunc("/", log(fileServer.ServeHTTP)) - - port := os.Args[1] - fmt.Println("Listening on port " + port) - err := http.ListenAndServe(":"+port, nil) - if err != nil { - fmt.Printf("Error while starting server: %v", err) - } -} - -var healthy = true - -func fail(w http.ResponseWriter, req *http.Request) { - healthy = false - fmt.Println("Server failing") -} - -func healthz(w http.ResponseWriter, r *http.Request) { - if !healthy { - fmt.Println("unhealthy") - w.WriteHeader(http.StatusServiceUnavailable) - return - } -} - -func log(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - fmt.Println(r.Method, r.URL.Path, r.RemoteAddr, r.UserAgent()) - handler(w, r) - } -} diff --git a/aci/e2e/e2e-aci_test.go b/aci/e2e/e2e-aci_test.go deleted file mode 100644 index 5a15fa5c2..000000000 --- a/aci/e2e/e2e-aci_test.go +++ /dev/null @@ -1,1016 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package main - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "math/rand" - "net/http" - "net/url" - "os" - "path/filepath" - "runtime" - "strconv" - "strings" - "syscall" - "testing" - "time" - - "github.com/Azure/azure-sdk-for-go/profiles/2019-03-01/resources/mgmt/resources" - "github.com/Azure/azure-storage-file-go/azfile" - "github.com/Azure/go-autorest/autorest/to" - "github.com/docker/docker/pkg/fileutils" - "github.com/prometheus/tsdb/fileutil" - "gotest.tools/v3/assert" - is "gotest.tools/v3/assert/cmp" - "gotest.tools/v3/icmd" - "gotest.tools/v3/poll" - - "github.com/docker/compose-cli/aci" - "github.com/docker/compose-cli/aci/convert" - "github.com/docker/compose-cli/aci/login" - "github.com/docker/compose-cli/api/containers" - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/cli/cmd" - "github.com/docker/compose-cli/pkg/api" - . "github.com/docker/compose-cli/utils/e2e" -) - -const ( - contextName = "aci-test" -) - -var ( - binDir string - location = []string{"eastus2"} -) - -func TestMain(m *testing.M) { - p, cleanup, err := SetupExistingCLI() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - binDir = p - exitCode := m.Run() - cleanup() - os.Exit(exitCode) -} - -// Cannot be parallelized as login/logout is global. -func TestLoginLogout(t *testing.T) { - startTime := strconv.Itoa(int(time.Now().UnixNano())) - c := NewE2eCLI(t, binDir) - rg := "E2E-" + startTime - - t.Run("login", func(t *testing.T) { - azureLogin(t, c) - }) - - t.Run("create context", func(t *testing.T) { - sID := getSubscriptionID(t) - location := getTestLocation() - err := createResourceGroup(t, sID, rg, location) - assert.Check(t, is.Nil(err)) - t.Cleanup(func() { - _ = deleteResourceGroup(t, rg) - }) - - c.RunDockerCmd("context", "create", "aci", contextName, "--subscription-id", sID, "--resource-group", rg, "--location", location) - res := c.RunDockerCmd("context", "use", contextName) - res.Assert(t, icmd.Expected{Out: contextName}) - res = c.RunDockerCmd("context", "ls") - res.Assert(t, icmd.Expected{Out: contextName + " *"}) - }) - - t.Run("delete context", func(t *testing.T) { - res := c.RunDockerCmd("context", "use", "default") - res.Assert(t, icmd.Expected{Out: "default"}) - - res = c.RunDockerCmd("context", "rm", contextName) - res.Assert(t, icmd.Expected{Out: contextName}) - }) - - t.Run("logout", func(t *testing.T) { - _, err := os.Stat(login.GetTokenStorePath()) - assert.NilError(t, err) - res := c.RunDockerCmd("logout", "azure") - res.Assert(t, icmd.Expected{Out: "Removing login credentials for Azure"}) - _, err = os.Stat(login.GetTokenStorePath()) - errMsg := "no such file or directory" - if runtime.GOOS == "windows" { - errMsg = "The system cannot find the file specified" - } - assert.ErrorContains(t, err, errMsg) - }) - - t.Run("create context fail", func(t *testing.T) { - res := c.RunDockerOrExitError("context", "create", "aci", "fail-context") - res.Assert(t, icmd.Expected{ - ExitCode: api.ExitCodeLoginRequired, - Err: `not logged in to azure, you need to run "docker login azure" first`, - }) - }) -} - -func getTestLocation() string { - rand.Seed(time.Now().Unix()) - n := rand.Intn(len(location)) - return location[n] -} - -func uploadTestFile(t *testing.T, aciContext store.AciContext, accountName string, fileshareName string, testFileName string, testFileContent string) { - storageLogin := login.StorageLoginImpl{AciContext: aciContext} - key, err := storageLogin.GetAzureStorageAccountKey(context.TODO(), accountName) - assert.NilError(t, err) - cred, err := azfile.NewSharedKeyCredential(accountName, key) - assert.NilError(t, err) - u, _ := url.Parse(fmt.Sprintf("https://%s.file.core.windows.net/%s", accountName, fileshareName)) - uploadFile(t, *cred, u.String(), testFileName, testFileContent) -} - -const ( - fileshareName = "dockertestshare" - fileshareName2 = "dockertestshare2" -) - -func TestRunVolume(t *testing.T) { - const ( - testFileContent = "Volume mounted successfully!" - testFileName = "index.html" - ) - - c := NewParallelE2eCLI(t, binDir) - sID, rg, location := setupTestResourceGroup(t, c) - - // Bootstrap volume - aciContext := store.AciContext{ - SubscriptionID: sID, - Location: location, - ResourceGroup: rg, - } - - // Used in subtests - var ( - container string - hostIP string - endpoint string - volumeID string - accountName = "e2e" + strconv.Itoa(int(time.Now().UnixNano())) - ) - - t.Run("check empty volume name validity", func(t *testing.T) { - invalidName := "" - res := c.RunDockerOrExitError("volume", "create", "--storage-account", invalidName, fileshareName) - res.Assert(t, icmd.Expected{ - ExitCode: 1, - Err: `parameter=accountName constraint=MinLength value="" details: value length must be greater than or equal to 3`, - }) - }) - - t.Run("check volume name validity", func(t *testing.T) { - invalidName := "some-storage-123" - res := c.RunDockerOrExitError("volume", "create", "--storage-account", invalidName, fileshareName) - res.Assert(t, icmd.Expected{ - ExitCode: 1, - Err: "some-storage-123 is not a valid storage account name. Storage account name must be between 3 and 24 characters in length and use numbers and lower-case letters only.", - }) - }) - - t.Run("create volumes", func(t *testing.T) { - c.RunDockerCmd("volume", "create", "--storage-account", accountName, fileshareName) - }) - - volumeID = accountName + "/" + fileshareName - t.Cleanup(func() { - c.RunDockerCmd("volume", "rm", volumeID) - res := c.RunDockerCmd("volume", "ls") - lines := Lines(res.Stdout()) - assert.Equal(t, len(lines), 1) - }) - - t.Run("create second fileshare", func(t *testing.T) { - c.RunDockerCmd("volume", "create", "--storage-account", accountName, fileshareName2) - }) - volumeID2 := accountName + "/" + fileshareName2 - - t.Run("list volumes", func(t *testing.T) { - res := c.RunDockerCmd("volume", "ls", "--quiet") - l := Lines(res.Stdout()) - assert.Equal(t, len(l), 2) - assert.Equal(t, l[0], volumeID) - assert.Equal(t, l[1], volumeID2) - - res = c.RunDockerCmd("volume", "ls") - l = Lines(res.Stdout()) - assert.Equal(t, len(l), 3) - firstAccount := l[1] - fields := strings.Fields(firstAccount) - assert.Equal(t, fields[0], volumeID) - secondAccount := l[2] - fields = strings.Fields(secondAccount) - assert.Equal(t, fields[0], volumeID2) - }) - - t.Run("inspect volumes", func(t *testing.T) { - res := c.RunDockerCmd("volume", "inspect", volumeID) - assert.Equal(t, res.Stdout(), fmt.Sprintf(`{ - "ID": %q, - "Description": "Fileshare %s in %s storage account" -} -`, volumeID, fileshareName, accountName)) - res = c.RunDockerCmd("volume", "inspect", volumeID2) - assert.Equal(t, res.Stdout(), fmt.Sprintf(`{ - "ID": %q, - "Description": "Fileshare %s in %s storage account" -} -`, volumeID2, fileshareName2, accountName)) - }) - - t.Run("delete only fileshare", func(t *testing.T) { - c.RunDockerCmd("volume", "rm", volumeID2) - res := c.RunDockerCmd("volume", "ls") - lines := Lines(res.Stdout()) - assert.Equal(t, len(lines), 2) - assert.Assert(t, !strings.Contains(res.Stdout(), fileshareName2), "second fileshare still visible after rm") - }) - - t.Run("upload file", func(t *testing.T) { - uploadTestFile(t, aciContext, accountName, fileshareName, testFileName, testFileContent) - }) - - t.Run("run", func(t *testing.T) { - mountTarget := "/usr/share/nginx/html" - res := c.RunDockerCmd( - "run", "-d", - "-v", fmt.Sprintf("%s:%s", volumeID, mountTarget), - "-p", "80:80", - "nginx", - ) - container = getContainerName(res.Stdout()) - }) - - t.Run("inspect", func(t *testing.T) { - res := c.RunDockerCmd("inspect", container) - - containerInspect, err := parseContainerInspect(res.Stdout()) - assert.NilError(t, err) - assert.Equal(t, containerInspect.Platform, "Linux") - assert.Equal(t, containerInspect.HostConfig.CPULimit, 1.0) - assert.Equal(t, containerInspect.HostConfig.CPUReservation, 1.0) - assert.Equal(t, containerInspect.HostConfig.RestartPolicy, containers.RestartPolicyNone) - - assert.Assert(t, is.Len(containerInspect.Ports, 1)) - hostIP = containerInspect.Ports[0].HostIP - endpoint = fmt.Sprintf("http://%s:%d", containerInspect.Ports[0].HostIP, containerInspect.Ports[0].HostPort) - }) - - t.Run("ps", func(t *testing.T) { - res := c.RunDockerCmd("ps") - out := Lines(res.Stdout()) - l := out[len(out)-1] - assert.Assert(t, strings.Contains(l, container), "Looking for %q in line: %s", container, l) - assert.Assert(t, strings.Contains(l, "nginx")) - assert.Assert(t, strings.Contains(l, "Running")) - assert.Assert(t, strings.Contains(l, hostIP+":80->80/tcp")) - }) - - t.Run("http get", func(t *testing.T) { - output := HTTPGetWithRetry(t, endpoint, http.StatusOK, 2*time.Second, 20*time.Second) - assert.Assert(t, strings.Contains(output, testFileContent), "Actual content: "+output) - }) - - t.Run("logs", func(t *testing.T) { - res := c.RunDockerCmd("logs", container) - res.Assert(t, icmd.Expected{Out: "GET"}) - }) - - t.Run("exec", func(t *testing.T) { - res := c.RunDockerOrExitError("exec", container, "pwd") - assert.Assert(t, strings.Contains(res.Stdout(), "/")) - - res = c.RunDockerOrExitError("exec", container, "echo", "fail_with_argument") - res.Assert(t, icmd.Expected{ - ExitCode: 1, - Err: "ACI exec command does not accept arguments to the command. Only the binary should be specified", - }) - }) - - t.Run("logs follow", func(t *testing.T) { - cmd := c.NewDockerCmd("logs", "--follow", container) - res := icmd.StartCmd(cmd) - - checkUp := func(t poll.LogT) poll.Result { - r, _ := http.Get(endpoint + "/is_up") - if r != nil && r.StatusCode == http.StatusNotFound { - return poll.Success() - } - return poll.Continue("waiting for container to serve request") - } - poll.WaitOn(t, checkUp, poll.WithDelay(1*time.Second), poll.WithTimeout(60*time.Second)) - - assert.Assert(t, !strings.Contains(res.Stdout(), "/test")) - - checkLogs := func(t poll.LogT) poll.Result { - if strings.Contains(res.Stdout(), "/test") { - return poll.Success() - } - return poll.Continue("waiting for logs to contain /test") - } - - // Do request on /test - go func() { - time.Sleep(3 * time.Second) - _, _ = http.Get(endpoint + "/test") - }() - - poll.WaitOn(t, checkLogs, poll.WithDelay(3*time.Second), poll.WithTimeout(20*time.Second)) - - if runtime.GOOS == "windows" { - err := res.Cmd.Process.Kill() - assert.NilError(t, err) - } else { - err := res.Cmd.Process.Signal(syscall.SIGTERM) - assert.NilError(t, err) - } - }) - - t.Run("rm a running container", func(t *testing.T) { - res := c.RunDockerOrExitError("rm", container) - res.Assert(t, icmd.Expected{ - Err: fmt.Sprintf("Error: you cannot remove a running container %s. Stop the container before attempting removal or force remove", container), - ExitCode: 1, - }) - }) - - t.Run("force rm", func(t *testing.T) { - res := c.RunDockerCmd("rm", "-f", container) - res.Assert(t, icmd.Expected{Out: container}) - - checkStopped := func(t poll.LogT) poll.Result { - res := c.RunDockerOrExitError("inspect", container) - if res.ExitCode == 1 { - return poll.Success() - } - return poll.Continue("waiting for container to stop") - } - poll.WaitOn(t, checkStopped, poll.WithDelay(5*time.Second), poll.WithTimeout(60*time.Second)) - }) -} - -func TestContainerRunAttached(t *testing.T) { - c := NewParallelE2eCLI(t, binDir) - _, groupID, location := setupTestResourceGroup(t, c) - - // Used in subtests - var ( - container = "test-container" - endpoint string - followLogsProcess *icmd.Result - ) - - t.Run("run attached limits", func(t *testing.T) { - dnsLabelName := "nginx-" + groupID - fqdn := dnsLabelName + "." + location + ".azurecontainer.io" - - cmd := c.NewDockerCmd( - "run", - "--name", container, - "--restart", "on-failure", - "--memory", "0.1G", "--cpus", "0.1", - "-p", "80:80", - "--domainname", - dnsLabelName, - "nginx", - ) - followLogsProcess = icmd.StartCmd(cmd) - - checkRunning := func(t poll.LogT) poll.Result { - res := c.RunDockerOrExitError("inspect", container) - if res.ExitCode == 0 && strings.Contains(res.Stdout(), `"Status": "Running"`) && !strings.Contains(res.Stdout(), `"HostIP": ""`) { - return poll.Success() - } - return poll.Continue("waiting for container to be running, current inspect result: \n%s", res.Combined()) - } - poll.WaitOn(t, checkRunning, poll.WithDelay(5*time.Second), poll.WithTimeout(180*time.Second)) - - inspectRes := c.RunDockerCmd("inspect", container) - - containerInspect, err := parseContainerInspect(inspectRes.Stdout()) - assert.NilError(t, err) - assert.Equal(t, containerInspect.Platform, "Linux") - assert.Equal(t, containerInspect.HostConfig.CPULimit, 0.1) - assert.Equal(t, containerInspect.HostConfig.MemoryLimit, uint64(107374182)) - assert.Equal(t, containerInspect.HostConfig.CPUReservation, 0.1) - assert.Equal(t, containerInspect.HostConfig.MemoryReservation, uint64(107374182)) - assert.Equal(t, containerInspect.HostConfig.RestartPolicy, containers.RestartPolicyOnFailure) - - assert.Assert(t, is.Len(containerInspect.Ports, 1)) - port := containerInspect.Ports[0] - assert.Assert(t, port.HostIP != "", "empty hostIP, inspect: \n"+inspectRes.Stdout()) - assert.Equal(t, port.ContainerPort, uint32(80)) - assert.Equal(t, port.HostPort, uint32(80)) - assert.Equal(t, containerInspect.Config.FQDN, fqdn) - endpoint = fmt.Sprintf("http://%s:%d", fqdn, port.HostPort) - - assert.Assert(t, !strings.Contains(followLogsProcess.Stdout(), "/test")) - HTTPGetWithRetry(t, endpoint+"/test", http.StatusNotFound, 2*time.Second, 60*time.Second) - - checkLog := func(t poll.LogT) poll.Result { - if strings.Contains(followLogsProcess.Stdout(), "/test") { - return poll.Success() - } - return poll.Continue("waiting for logs to contain /test") - } - poll.WaitOn(t, checkLog, poll.WithDelay(1*time.Second), poll.WithTimeout(20*time.Second)) - }) - - t.Run("stop wrong container", func(t *testing.T) { - res := c.RunDockerOrExitError("stop", "unknown-container") - res.Assert(t, icmd.Expected{ - Err: "Error: container unknown-container not found", - ExitCode: 1, - }) - }) - - t.Run("stop container", func(t *testing.T) { - res := c.RunDockerCmd("stop", container) - res.Assert(t, icmd.Expected{Out: container}) - waitForStatus(t, c, container, "Terminated", "Node Stopped") - }) - - t.Run("check we stoppped following logs", func(t *testing.T) { - // nolint errcheck - followLogsStopped := waitWithTimeout(func() { followLogsProcess.Cmd.Process.Wait() }, 10*time.Second) - assert.NilError(t, followLogsStopped, "Follow logs process did not stop after container is stopped") - }) - - t.Run("ps stopped container with --all", func(t *testing.T) { - res := c.RunDockerCmd("ps", container) - out := Lines(res.Stdout()) - assert.Assert(t, is.Len(out, 1)) - - res = c.RunDockerCmd("ps", "--all", container) - out = Lines(res.Stdout()) - assert.Assert(t, is.Len(out, 2)) - }) - - t.Run("restart container", func(t *testing.T) { - res := c.RunDockerOrExitError("start", container) - //Flaky errors on restart : Code="ContainerGroupTransitioning" Message="The container group 'test-container' is still transitioning, please retry later." - if res.ExitCode != 0 && strings.Contains(res.Stderr(), `Code="ContainerGroupTransitioning"`) { - res = c.RunDockerOrExitError("rm", "-f", container) - if strings.Contains(res.Stderr(), "unsupported protocol scheme") { // ... - time.Sleep(1 * time.Second) - c.RunDockerCmd("rm", "-f", container) - } - go func() { // this specific call to run sometimes does not come back when in this weird state, but the container is actually running fine - c.RunDockerCmd("run", - "--name", container, // don't reuse the container name, this container is in a weird state and blocks everything - "--memory", "0.1G", "--cpus", "0.1", - "nginx") - fmt.Printf(" [%s] Finished docker run %s\n", t.Name(), container) - }() - waitForStatus(t, c, container, convert.StatusRunning) - } else { - res.Assert(t, icmd.Expected{Out: container}) - waitForStatus(t, c, container, convert.StatusRunning) - } - }) - - t.Run("prune dry run", func(t *testing.T) { - res := c.RunDockerCmd("prune", "--dry-run") - assert.Equal(t, "Resources that would be deleted:\nTotal CPUs reclaimed: 0.00, total memory reclaimed: 0.00 GB\n", res.Stdout()) - res = c.RunDockerOrExitError("prune", "--dry-run", "--force") - if strings.Contains(res.Stderr(), "unsupported protocol scheme") { //Flaky strange error on azure SDK call happening only during prune --force - time.Sleep(1 * time.Second) - res = c.RunDockerCmd("prune", "--dry-run", "--force") - } - assert.Equal(t, "Resources that would be deleted:\n"+container+"\nTotal CPUs reclaimed: 0.10, total memory reclaimed: 0.10 GB\n", res.Stdout()) - }) - - t.Run("prune", func(t *testing.T) { - res := c.RunDockerCmd("prune") - assert.Equal(t, "Deleted resources:\nTotal CPUs reclaimed: 0.00, total memory reclaimed: 0.00 GB\n", res.Stdout()) - res = c.RunDockerCmd("ps") - l := Lines(res.Stdout()) - assert.Equal(t, 2, len(l)) - - res = c.RunDockerOrExitError("prune", "--force") - if strings.Contains(res.Stderr(), "unsupported protocol scheme") { //Flaky strange error on azure SDK call happening only during prune --force - time.Sleep(1 * time.Second) - res = c.RunDockerCmd("prune", "--force") - // After the retry, it seems prune has sometimes actually been executed, and we get zero thigs to delete again... - assert.Assert(t, res.Stdout() == "Deleted resources:\n"+container+"\nTotal CPUs reclaimed: 0.10, total memory reclaimed: 0.10 GB\n" || - res.Stdout() == "Deleted resources:\nTotal CPUs reclaimed: 0.00, total memory reclaimed: 0.00 GB\n", res.Stdout()) - } else { - assert.Equal(t, "Deleted resources:\n"+container+"\nTotal CPUs reclaimed: 0.10, total memory reclaimed: 0.10 GB\n", res.Stdout()) - } - - res = c.RunDockerCmd("ps", "--all") - l = Lines(res.Stdout()) - assert.Equal(t, 1, len(l)) - }) -} - -func overwriteFileStorageAccount(t *testing.T, absComposefileName string, storageAccount string) { - data, err := ioutil.ReadFile(absComposefileName) - assert.NilError(t, err) - override := strings.Replace(string(data), "dockertestvolumeaccount", storageAccount, 1) - err = ioutil.WriteFile(absComposefileName, []byte(override), 0644) - assert.NilError(t, err) -} - -func TestUpSecretsResources(t *testing.T) { - const ( - composeProjectName = "aci_test" - web1 = composeProjectName + "_web1" - web2 = composeProjectName + "_web2" - - secret1Name = "mytarget1" - secret1Value = "myPassword1\n" - - secret2Name = "mysecret2" - secret2Value = "another_password\n" - ) - - composefilePath := filepath.Join("aci_secrets_resources", "compose.yaml") - - c := NewParallelE2eCLI(t, binDir) - _, _, _ = setupTestResourceGroup(t, c) - - t.Run("compose up", func(t *testing.T) { - c.RunDockerCmd("compose", "-f", composefilePath, "--project-name", composeProjectName, "up") - res := c.RunDockerCmd("ps") - out := Lines(res.Stdout()) - // Check 2 containers running - assert.Assert(t, is.Len(out, 3)) - }) - - t.Cleanup(func() { - c.RunDockerCmd("compose", "--project-name", composeProjectName, "down") - res := c.RunDockerCmd("ps") - out := Lines(res.Stdout()) - assert.Equal(t, len(out), 1) - }) - - res := c.RunDockerCmd("inspect", web1) - web1Inspect, err := parseContainerInspect(res.Stdout()) - assert.NilError(t, err) - res = c.RunDockerCmd("inspect", web2) - web2Inspect, err := parseContainerInspect(res.Stdout()) - assert.NilError(t, err) - - t.Run("read secrets in service 1", func(t *testing.T) { - assert.Assert(t, is.Len(web1Inspect.Ports, 1)) - endpoint := fmt.Sprintf("http://%s:%d", web1Inspect.Ports[0].HostIP, web1Inspect.Ports[0].HostPort) - - output := HTTPGetWithRetry(t, endpoint+"/"+secret1Name, http.StatusOK, 2*time.Second, 20*time.Second) - // replace windows carriage return - output = strings.ReplaceAll(output, "\r", "") - assert.Equal(t, output, secret1Value) - - output = HTTPGetWithRetry(t, endpoint+"/"+secret2Name, http.StatusOK, 2*time.Second, 20*time.Second) - output = strings.ReplaceAll(output, "\r", "") - assert.Equal(t, output, secret2Value) - }) - - t.Run("read secrets in service 2", func(t *testing.T) { - assert.Assert(t, is.Len(web2Inspect.Ports, 1)) - endpoint := fmt.Sprintf("http://%s:%d", web2Inspect.Ports[0].HostIP, web2Inspect.Ports[0].HostPort) - - output := HTTPGetWithRetry(t, endpoint+"/"+secret2Name, http.StatusOK, 2*time.Second, 20*time.Second) - output = strings.ReplaceAll(output, "\r", "") - assert.Equal(t, output, secret2Value) - - HTTPGetWithRetry(t, endpoint+"/"+secret1Name, http.StatusNotFound, 2*time.Second, 20*time.Second) - }) - - t.Run("check resource limits", func(t *testing.T) { - assert.Equal(t, web1Inspect.HostConfig.CPULimit, 0.7) - assert.Equal(t, web1Inspect.HostConfig.MemoryLimit, uint64(1073741824)) - assert.Equal(t, web1Inspect.HostConfig.CPUReservation, 0.5) - assert.Equal(t, web1Inspect.HostConfig.MemoryReservation, uint64(536870912)) - - assert.Equal(t, web2Inspect.HostConfig.CPULimit, 0.5) - assert.Equal(t, web2Inspect.HostConfig.MemoryLimit, uint64(751619276)) - assert.Equal(t, web2Inspect.HostConfig.CPUReservation, 0.5) - assert.Equal(t, web2Inspect.HostConfig.MemoryReservation, uint64(751619276)) - }) - - t.Run("check healthchecks inspect", func(t *testing.T) { - assert.Assert(t, web1Inspect.Healthcheck != nil) - assert.Equal(t, time.Duration(*web1Inspect.Healthcheck.Interval), 5*time.Second) - assert.DeepEqual(t, web1Inspect.Healthcheck.Test, []string{"curl", "-f", "http://localhost:80/healthz"}) - - assert.Assert(t, web2Inspect.Healthcheck == nil) - }) - - t.Run("healthcheck restart failed app", func(t *testing.T) { - endpoint := fmt.Sprintf("http://%s:%d", web1Inspect.Ports[0].HostIP, web1Inspect.Ports[0].HostPort) - HTTPGetWithRetry(t, endpoint+"/failtestserver", http.StatusOK, 3*time.Second, 3*time.Second) - - logs := c.RunDockerCmd("logs", web1).Combined() - assert.Assert(t, strings.Contains(logs, "GET /healthz")) - assert.Assert(t, strings.Contains(logs, "GET /failtestserver")) - assert.Assert(t, strings.Contains(logs, "Server failing")) - - checkLogsReset := func(logt poll.LogT) poll.Result { - res := c.RunDockerOrExitError("logs", web1) - if res.ExitCode == 0 && - !strings.Contains(res.Combined(), "GET /failtestserver") && - strings.Contains(res.Combined(), "Listening on port 80") && - strings.Contains(res.Combined(), "GET /healthz") { - return poll.Success() - } - return poll.Continue("Logs not reset by healcheck restart\n" + res.Combined()) - } - - poll.WaitOn(t, checkLogsReset, poll.WithDelay(5*time.Second), poll.WithTimeout(90*time.Second)) - - res := c.RunDockerCmd("inspect", web1) - web1Inspect, err = parseContainerInspect(res.Stdout()) - assert.Equal(t, web1Inspect.Status, "Running") - }) -} - -func TestUpUpdate(t *testing.T) { - const ( - composeProjectName = "acidemo" - serverContainer = composeProjectName + "_web" - wordsContainer = composeProjectName + "_words" - dbContainer = composeProjectName + "_db" - ) - var ( - singlePortVolumesComposefile = "aci_demo_port_volumes.yaml" - multiPortComposefile = "demo_multi_port.yaml" - ) - c := NewParallelE2eCLI(t, binDir) - sID, groupID, location := setupTestResourceGroup(t, c) - composeAccountName := strings.ToLower(strings.ReplaceAll(groupID, "-", "") + "sa") - - dstDir := filepath.Join(os.TempDir(), "e2e-aci-volume-"+composeAccountName) - err := fileutil.CopyDirs("aci-demo", dstDir) - assert.NilError(t, err) - t.Cleanup(func() { - assert.NilError(t, os.RemoveAll(dstDir)) - }) - _, err = fileutils.CopyFile(filepath.Join(filepath.Join("aci-demo"), multiPortComposefile), filepath.Join(dstDir, multiPortComposefile)) - assert.NilError(t, err) - - singlePortVolumesComposefile = filepath.Join(dstDir, singlePortVolumesComposefile) - overwriteFileStorageAccount(t, singlePortVolumesComposefile, composeAccountName) - multiPortComposefile = filepath.Join(dstDir, multiPortComposefile) - - volumeID := composeAccountName + "/" + fileshareName - const ( - testFileName = "msg.txt" - testFileContent = "VOLUME_OK" - projectName = "acidemo" - ) - var ( - dnsLabelName = "nginx-" + groupID - fqdn = dnsLabelName + "." + location + ".azurecontainer.io" - ) - - t.Run("compose up", func(t *testing.T) { - aciContext := store.AciContext{ - SubscriptionID: sID, - Location: location, - ResourceGroup: groupID, - } - c.RunDockerCmd("compose", "-f", singlePortVolumesComposefile, "--project-name", projectName, "up", "--domainname", dnsLabelName) - - // Volume should be autocreated by the "compose up" - uploadTestFile(t, aciContext, composeAccountName, fileshareName, testFileName, testFileContent) - }) - - t.Cleanup(func() { - c.RunDockerCmd("volume", "rm", volumeID) - }) - - t.Run("check deployed compose app", func(t *testing.T) { - res := c.RunDockerCmd("ps") - out := Lines(res.Stdout()) - // Check three containers are running - assert.Assert(t, is.Len(out, 4)) - webRunning := false - for _, l := range out { - if strings.Contains(l, serverContainer) { - webRunning = true - assert.Check(t, strings.Contains(l, ":80->80/tcp")) - } - } - assert.Assert(t, webRunning, "web container not running ; ps:\n"+res.Stdout()) - - res = c.RunDockerCmd("inspect", serverContainer) - - containerInspect, err := parseContainerInspect(res.Stdout()) - assert.NilError(t, err) - assert.Assert(t, is.Len(containerInspect.Ports, 1)) - endpoint := fmt.Sprintf("http://%s:%d", containerInspect.Ports[0].HostIP, containerInspect.Ports[0].HostPort) - - output := HTTPGetWithRetry(t, endpoint+"/words/noun", http.StatusOK, 2*time.Second, 20*time.Second) - - assert.Assert(t, strings.Contains(output, `"word":`)) - - endpoint = fmt.Sprintf("http://%s:%d", fqdn, containerInspect.Ports[0].HostPort) - HTTPGetWithRetry(t, endpoint+"/words/noun", http.StatusOK, 2*time.Second, 20*time.Second) - - body := HTTPGetWithRetry(t, endpoint+"/volume_test/"+testFileName, http.StatusOK, 2*time.Second, 20*time.Second) - assert.Assert(t, strings.Contains(body, testFileContent)) - - // Try to remove the volume while it's still in use - res = c.RunDockerOrExitError("volume", "rm", volumeID) - res.Assert(t, icmd.Expected{ - ExitCode: 1, - Err: fmt.Sprintf(`Error: volume "%s/%s" is used in container group %q`, - composeAccountName, fileshareName, projectName), - }) - }) - - t.Run("compose ps", func(t *testing.T) { - res := c.RunDockerCmd("compose", "--project-name", composeProjectName, "ps", "--quiet") - l := Lines(res.Stdout()) - assert.Assert(t, is.Len(l, 3)) - - res = c.RunDockerCmd("compose", "--project-name", composeProjectName, "ps") - l = Lines(res.Stdout()) - assert.Assert(t, is.Len(l, 4)) - var wordsDisplayed, webDisplayed, dbDisplayed bool - for _, line := range l { - fields := strings.Fields(line) - name := fields[0] - switch name { - case wordsContainer: - wordsDisplayed = true - assert.Equal(t, fields[2], "Running") - case dbContainer: - dbDisplayed = true - assert.Equal(t, fields[2], "Running") - case serverContainer: - webDisplayed = true - assert.Equal(t, fields[2], "Running") - assert.Check(t, strings.Contains(fields[3], ":80->80/tcp")) - } - } - assert.Check(t, webDisplayed, "webDisplayed"+res.Stdout()) - assert.Check(t, wordsDisplayed, "wordsDisplayed"+res.Stdout()) - assert.Check(t, dbDisplayed, "dbDisplayed"+res.Stdout()) - assert.Check(t, webDisplayed && wordsDisplayed && dbDisplayed, "\n%s\n", res.Stdout()) - }) - - t.Run("compose ls", func(t *testing.T) { - res := c.RunDockerCmd("compose", "ls", "--quiet") - l := Lines(res.Stdout()) - assert.Assert(t, is.Len(l, 1)) - res = c.RunDockerCmd("compose", "ls") - l = Lines(res.Stdout()) - - assert.Equal(t, 2, len(l)) - fields := strings.Fields(l[1]) - assert.Equal(t, 2, len(fields)) - assert.Equal(t, fields[0], composeProjectName) - assert.Equal(t, "Running", fields[1]) - }) - - t.Run("logs web", func(t *testing.T) { - res := c.RunDockerCmd("logs", serverContainer) - res.Assert(t, icmd.Expected{Out: "Listening on port 80"}) - }) - - t.Run("update", func(t *testing.T) { - c.RunDockerCmd("compose", "-f", multiPortComposefile, "--project-name", composeProjectName, "up") - res := c.RunDockerCmd("ps") - out := Lines(res.Stdout()) - // Check three containers are running - assert.Assert(t, is.Len(out, 4)) - - for _, cName := range []string{serverContainer, wordsContainer} { - res = c.RunDockerCmd("inspect", cName) - - containerInspect, err := parseContainerInspect(res.Stdout()) - assert.NilError(t, err) - assert.Assert(t, is.Len(containerInspect.Ports, 1)) - endpoint := fmt.Sprintf("http://%s:%d", containerInspect.Ports[0].HostIP, containerInspect.Ports[0].HostPort) - var route string - switch cName { - case serverContainer: - route = "/words/noun" - assert.Equal(t, containerInspect.Ports[0].HostPort, uint32(80)) - assert.Equal(t, containerInspect.Ports[0].ContainerPort, uint32(80)) - case wordsContainer: - route = "/noun" - assert.Equal(t, containerInspect.Ports[0].HostPort, uint32(8080)) - assert.Equal(t, containerInspect.Ports[0].ContainerPort, uint32(8080)) - } - HTTPGetWithRetry(t, endpoint+route, http.StatusOK, 1*time.Second, 60*time.Second) - - res = c.RunDockerCmd("ps") - p := containerInspect.Ports[0] - res.Assert(t, icmd.Expected{ - Out: fmt.Sprintf("%s:%d->%d/tcp", p.HostIP, p.HostPort, p.ContainerPort), - }) - } - }) - - t.Run("down", func(t *testing.T) { - c.RunDockerCmd("compose", "--project-name", composeProjectName, "down") - res := c.RunDockerCmd("ps") - out := Lines(res.Stdout()) - assert.Equal(t, len(out), 1) - }) -} - -/* -func TestRunEnvVars(t *testing.T) { - c := NewParallelE2eCLI(t, binDir) - _, _, _ = setupTestResourceGroup(t, c) - - t.Run("run", func(t *testing.T) { - cmd := c.NewDockerCmd( - "run", "-d", - "-e", "MYSQL_ROOT_PASSWORD=rootpwd", - "-e", "MYSQL_DATABASE=mytestdb", - "-e", "MYSQL_USER", - "-e", "MYSQL_PASSWORD=userpwd", - "-e", "DATASOURCE_URL=jdbc:mysql://mydb.mysql.database.azure.com/db1?useSSL=true&requireSSL=false&serverTimezone=America/Recife", - "mysql:5.7", - ) - cmd.Env = append(cmd.Env, "MYSQL_USER=user1") - res := icmd.RunCmd(cmd) - res.Assert(t, icmd.Success) - out := Lines(res.Stdout()) - container := strings.TrimSpace(out[len(out)-1]) - - res = c.RunDockerCmd("inspect", container) - - containerInspect, err := ParseContainerInspect(res.Stdout()) - assert.NilError(t, err) - assert.Assert(t, containerInspect.Config != nil, "nil container config") - assert.Assert(t, containerInspect.Config.Env != nil, "nil container env variables") - assert.Equal(t, containerInspect.Image, "mysql:5.7") - envVars := containerInspect.Config.Env - assert.Equal(t, len(envVars), 5) - assert.Equal(t, envVars["MYSQL_ROOT_PASSWORD"], "rootpwd") - assert.Equal(t, envVars["MYSQL_DATABASE"], "mytestdb") - assert.Equal(t, envVars["MYSQL_USER"], "user1") - assert.Equal(t, envVars["MYSQL_PASSWORD"], "userpwd") - assert.Equal(t, envVars["DATASOURCE_URL"], "jdbc:mysql://mydb.mysql.database.azure.com/db1?useSSL=true&requireSSL=false&serverTimezone=America/Recife") - - check := func(t poll.LogT) poll.Result { - res := c.RunDockerOrExitError("logs", container) - if strings.Contains(res.Stdout(), "Giving user user1 access to schema mytestdb") { - return poll.Success() - } - return poll.Continue("waiting for DB container to be up\n%s", res.Combined()) - } - poll.WaitOn(t, check, poll.WithDelay(5*time.Second), poll.WithTimeout(90*time.Second)) - }) -} -*/ - -func setupTestResourceGroup(t *testing.T, c *E2eCLI) (string, string, string) { - startTime := strconv.Itoa(int(time.Now().Unix())) - rg := "E2E-" + t.Name() + "-" + startTime[5:] - azureLogin(t, c) - sID := getSubscriptionID(t) - location := getTestLocation() - err := createResourceGroup(t, sID, rg, location) - assert.Check(t, is.Nil(err)) - t.Cleanup(func() { - if err := deleteResourceGroup(t, rg); err != nil { - t.Error(err) - } - }) - - createAciContextAndUseIt(t, c, sID, rg, location) - // Check nothing is running - res := c.RunDockerCmd("ps") - assert.Assert(t, is.Len(Lines(res.Stdout()), 1)) - return sID, rg, location -} - -func deleteResourceGroup(t *testing.T, rgName string) error { - fmt.Printf(" [%s] deleting resource group %s\n", t.Name(), rgName) - ctx := context.TODO() - helper := aci.NewACIResourceGroupHelper() - models, err := helper.GetSubscriptionIDs(ctx) - if err != nil { - return err - } - if len(models) == 0 { - return errors.New("unable to delete resource group: no models") - } - return helper.DeleteAsync(ctx, *models[0].SubscriptionID, rgName) -} - -func azureLogin(t *testing.T, c *E2eCLI) { - // in order to create new service principal and get these 3 values : `az ad sp create-for-rbac --name 'TestServicePrincipal' --sdk-auth` - clientID := os.Getenv("AZURE_CLIENT_ID") - clientSecret := os.Getenv("AZURE_CLIENT_SECRET") - tenantID := os.Getenv("AZURE_TENANT_ID") - assert.Check(t, clientID != "", "AZURE_CLIENT_ID must not be empty") - assert.Check(t, clientSecret != "", "AZURE_CLIENT_SECRET must not be empty") - assert.Check(t, tenantID != "", "AZURE_TENANT_ID must not be empty") - c.RunDockerCmd("login", "azure", "--client-id", clientID, "--client-secret", clientSecret, "--tenant-id", tenantID) -} - -func getSubscriptionID(t *testing.T) string { - ctx := context.TODO() - helper := aci.NewACIResourceGroupHelper() - models, err := helper.GetSubscriptionIDs(ctx) - assert.Check(t, is.Nil(err)) - assert.Check(t, len(models) == 1) - return *models[0].SubscriptionID -} - -func createResourceGroup(t *testing.T, sID, rgName string, location string) error { - fmt.Printf(" [%s] creating resource group %s\n", t.Name(), rgName) - helper := aci.NewACIResourceGroupHelper() - _, err := helper.CreateOrUpdate(context.TODO(), sID, rgName, resources.Group{Location: to.StringPtr(location)}) - return err -} - -func createAciContextAndUseIt(t *testing.T, c *E2eCLI, sID, rgName string, location string) { - res := c.RunDockerCmd("context", "create", "aci", contextName, "--subscription-id", sID, "--resource-group", rgName, "--location", location) - res.Assert(t, icmd.Expected{Out: "Successfully created aci context \"" + contextName + "\""}) - res = c.RunDockerCmd("context", "use", contextName) - res.Assert(t, icmd.Expected{Out: contextName}) - res = c.RunDockerCmd("context", "ls") - res.Assert(t, icmd.Expected{Out: contextName + " *"}) -} - -func uploadFile(t *testing.T, cred azfile.SharedKeyCredential, baseURL, fileName, content string) { - fURL, err := url.Parse(baseURL + "/" + fileName) - assert.NilError(t, err) - fileURL := azfile.NewFileURL(*fURL, azfile.NewPipeline(&cred, azfile.PipelineOptions{})) - err = azfile.UploadBufferToAzureFile(context.TODO(), []byte(content), fileURL, azfile.UploadToAzureFileOptions{}) - assert.NilError(t, err) -} - -func getContainerName(stdout string) string { - out := Lines(stdout) - return strings.TrimSpace(out[len(out)-1]) -} - -func waitForStatus(t *testing.T, c *E2eCLI, containerID string, statuses ...string) { - checkStopped := func(logt poll.LogT) poll.Result { - res := c.RunDockerOrExitError("inspect", containerID) - if res.Error != nil { - return poll.Continue("Error while inspecting container %s: %s", containerID, res.Combined()) - } - containerInspect, err := parseContainerInspect(res.Stdout()) - assert.NilError(t, err) - for _, status := range statuses { - if containerInspect.Status == status { - return poll.Success() - } - } - return poll.Continue("Status %s != %s (expected) for container %s", containerInspect.Status, statuses, containerID) - } - - poll.WaitOn(t, checkStopped, poll.WithDelay(5*time.Second), poll.WithTimeout(90*time.Second)) -} - -func parseContainerInspect(stdout string) (*cmd.ContainerInspectView, error) { - var res cmd.ContainerInspectView - rdr := bytes.NewReader([]byte(stdout)) - if err := json.NewDecoder(rdr).Decode(&res); err != nil { - return nil, err - } - return &res, nil -} - -func waitWithTimeout(blockingCall func(), timeout time.Duration) error { - c := make(chan struct{}) - go func() { - defer close(c) - blockingCall() - }() - select { - case <-c: - return nil - case <-time.After(timeout): - return fmt.Errorf("Timed out after %s", timeout) - } -} diff --git a/aci/etchosts/Dockerfile b/aci/etchosts/Dockerfile deleted file mode 100644 index d9b8e8850..000000000 --- a/aci/etchosts/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2020 Docker Compose CLI authors - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -FROM golang:1.16 AS builder -WORKDIR $GOPATH/src/github.com/docker/compose-cli/aci/etchosts -COPY . . -RUN GO111MODULE=auto CGO_ENABLED=0 go build -ldflags="-w -s" -o /go/bin/hosts main/main.go - -FROM scratch -COPY --from=builder /go/bin/hosts /hosts \ No newline at end of file diff --git a/aci/etchosts/hosts.go b/aci/etchosts/hosts.go deleted file mode 100644 index 4e856aa49..000000000 --- a/aci/etchosts/hosts.go +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package etchosts - -import ( - "fmt" - "os" - "strings" -) - -// SetHostNames appends hosts aliases for loopback address to etc/host file -func SetHostNames(file string, hosts ...string) error { - f, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - return err - } - defer f.Close() //nolint:errcheck - - fmt.Println("Setting local hosts for " + strings.Join(hosts, ", ")) - for _, host := range hosts { - _, err = f.WriteString("\n127.0.0.1 " + host) - if err != nil { - return err - } - } - _, err = f.WriteString("\n") - return err -} diff --git a/aci/etchosts/hosts_test.go b/aci/etchosts/hosts_test.go deleted file mode 100644 index d3da7edcd..000000000 --- a/aci/etchosts/hosts_test.go +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package etchosts - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" - - "gotest.tools/v3/assert" - "gotest.tools/v3/fs" - "gotest.tools/v3/golden" -) - -func TestSetDomain(t *testing.T) { - dir := fs.NewDir(t, "resolv").Path() - f := filepath.Join(dir, "hosts") - touch(t, f) - - err := SetHostNames(f, "foo", "bar", "zot") - assert.NilError(t, err) - - got, err := ioutil.ReadFile(f) - assert.NilError(t, err) - golden.Assert(t, string(got), "etchosts.golden") -} - -func touch(t *testing.T, f string) { - file, err := os.Create(f) - assert.NilError(t, err) - err = file.Close() - assert.NilError(t, err) -} diff --git a/aci/etchosts/main/main.go b/aci/etchosts/main/main.go deleted file mode 100644 index 925bb166d..000000000 --- a/aci/etchosts/main/main.go +++ /dev/null @@ -1,47 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package main - -import ( - "fmt" - "os" - "os/signal" - "syscall" - - "github.com/docker/compose-cli/aci/etchosts" -) - -const hosts = "/etc/hosts" - -func main() { - if len(os.Args) < 2 { - fmt.Fprint(os.Stderr, "usage: hosts HOSTNAME [HOSTNAME]") - os.Exit(1) - } - - err := etchosts.SetHostNames(hosts, os.Args[1:]...) - if err != nil { - fmt.Fprint(os.Stderr, err.Error()) - os.Exit(1) - } - - // ACI restart policy is currently at container group level, cannot let the sidecar terminate quietly once /etc/hosts has been edited - // Pause forever (until someone explicitly terminates this process ; go is not happy to stop all goroutines otherwise) - exitSignal := make(chan os.Signal, 1) - signal.Notify(exitSignal, syscall.SIGINT, syscall.SIGTERM) - <-exitSignal -} diff --git a/aci/etchosts/testdata/etchosts.golden b/aci/etchosts/testdata/etchosts.golden deleted file mode 100644 index 420b850b9..000000000 --- a/aci/etchosts/testdata/etchosts.golden +++ /dev/null @@ -1,4 +0,0 @@ - -127.0.0.1 foo -127.0.0.1 bar -127.0.0.1 zot diff --git a/aci/login/client.go b/aci/login/client.go deleted file mode 100644 index 79c3baf7e..000000000 --- a/aci/login/client.go +++ /dev/null @@ -1,153 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package login - -import ( - "encoding/json" - "strconv" - "time" - - "github.com/Azure/azure-sdk-for-go/profiles/2019-03-01/resources/mgmt/resources" - "github.com/Azure/azure-sdk-for-go/profiles/preview/preview/subscription/mgmt/subscription" - "github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2019-12-01/containerinstance" - "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage" - "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/adal" - "github.com/Azure/go-autorest/autorest/date" - "github.com/pkg/errors" - - "github.com/docker/compose-cli/internal" - "github.com/docker/compose-cli/pkg/api" -) - -// UserAgentName is the default user agent used by the cli -const UserAgentName = "docker-cli" - -// NewContainerGroupsClient get client toi manipulate containerGrouos -func NewContainerGroupsClient(subscriptionID string) (containerinstance.ContainerGroupsClient, error) { - authorizer, mgmtURL, err := getClientSetupData() - if err != nil { - return containerinstance.ContainerGroupsClient{}, err - } - containerGroupsClient := containerinstance.NewContainerGroupsClientWithBaseURI(mgmtURL, subscriptionID) - setupClient(&containerGroupsClient.Client, authorizer) - if err != nil { - return containerinstance.ContainerGroupsClient{}, err - } - containerGroupsClient.PollingDelay = 5 * time.Second - containerGroupsClient.RetryAttempts = 30 - containerGroupsClient.RetryDuration = 1 * time.Second - return containerGroupsClient, nil -} - -func setupClient(aciClient *autorest.Client, auth autorest.Authorizer) { - aciClient.UserAgent = UserAgentName + "/" + internal.Version - aciClient.Authorizer = auth -} - -// NewStorageAccountsClient get client to manipulate storage accounts -func NewStorageAccountsClient(subscriptionID string) (storage.AccountsClient, error) { - authorizer, mgmtURL, err := getClientSetupData() - if err != nil { - return storage.AccountsClient{}, err - } - storageAccuntsClient := storage.NewAccountsClientWithBaseURI(mgmtURL, subscriptionID) - setupClient(&storageAccuntsClient.Client, authorizer) - storageAccuntsClient.PollingDelay = 5 * time.Second - storageAccuntsClient.RetryAttempts = 30 - storageAccuntsClient.RetryDuration = 1 * time.Second - return storageAccuntsClient, nil -} - -// NewFileShareClient get client to manipulate file shares -func NewFileShareClient(subscriptionID string) (storage.FileSharesClient, error) { - authorizer, mgmtURL, err := getClientSetupData() - if err != nil { - return storage.FileSharesClient{}, err - } - fileSharesClient := storage.NewFileSharesClientWithBaseURI(mgmtURL, subscriptionID) - setupClient(&fileSharesClient.Client, authorizer) - fileSharesClient.PollingDelay = 5 * time.Second - fileSharesClient.RetryAttempts = 30 - fileSharesClient.RetryDuration = 1 * time.Second - return fileSharesClient, nil -} - -// NewSubscriptionsClient get subscription client -func NewSubscriptionsClient() (subscription.SubscriptionsClient, error) { - authorizer, mgmtURL, err := getClientSetupData() - if err != nil { - return subscription.SubscriptionsClient{}, errors.Wrap(api.ErrLoginRequired, err.Error()) - } - subc := subscription.NewSubscriptionsClientWithBaseURI(mgmtURL) - setupClient(&subc.Client, authorizer) - return subc, nil -} - -// NewGroupsClient get client to manipulate groups -func NewGroupsClient(subscriptionID string) (resources.GroupsClient, error) { - authorizer, mgmtURL, err := getClientSetupData() - if err != nil { - return resources.GroupsClient{}, err - } - groupsClient := resources.NewGroupsClientWithBaseURI(mgmtURL, subscriptionID) - setupClient(&groupsClient.Client, authorizer) - return groupsClient, nil -} - -// NewContainerClient get client to manipulate containers -func NewContainerClient(subscriptionID string) (containerinstance.ContainersClient, error) { - authorizer, mgmtURL, err := getClientSetupData() - if err != nil { - return containerinstance.ContainersClient{}, err - } - containerClient := containerinstance.NewContainersClientWithBaseURI(mgmtURL, subscriptionID) - setupClient(&containerClient.Client, authorizer) - return containerClient, nil -} - -func getClientSetupData() (autorest.Authorizer, string, error) { - return getClientSetupDataImpl(GetTokenStorePath()) -} - -func getClientSetupDataImpl(tokenStorePath string) (autorest.Authorizer, string, error) { - als, err := newAzureLoginServiceFromPath(tokenStorePath, azureAPIHelper{}, CloudEnvironments) - if err != nil { - return nil, "", err - } - - oauthToken, _, err := als.GetValidToken() - if err != nil { - return nil, "", errors.Wrap(err, "not logged in to azure, you need to run \"docker login azure\" first") - } - - ce, err := als.GetCloudEnvironment() - if err != nil { - return nil, "", err - } - - token := adal.Token{ - AccessToken: oauthToken.AccessToken, - Type: oauthToken.TokenType, - ExpiresIn: json.Number(strconv.Itoa(int(time.Until(oauthToken.Expiry).Seconds()))), - ExpiresOn: json.Number(strconv.Itoa(int(oauthToken.Expiry.Sub(date.UnixEpoch()).Seconds()))), - RefreshToken: "", - Resource: "", - } - - return autorest.NewBearerAuthorizer(&token), ce.ResourceManagerURL, nil -} diff --git a/aci/login/client_test.go b/aci/login/client_test.go deleted file mode 100644 index 4c77eec14..000000000 --- a/aci/login/client_test.go +++ /dev/null @@ -1,36 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package login - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" - - "gotest.tools/v3/assert" -) - -func TestClearErrorMessageIfNotAlreadyLoggedIn(t *testing.T) { - dir, err := ioutil.TempDir("", "test_store") - assert.NilError(t, err) - t.Cleanup(func() { - _ = os.RemoveAll(dir) - }) - _, _, err = getClientSetupDataImpl(filepath.Join(dir, tokenStoreFilename)) - assert.ErrorContains(t, err, "not logged in to azure, you need to run \"docker login azure\" first") -} diff --git a/aci/login/cloud_environment.go b/aci/login/cloud_environment.go deleted file mode 100644 index d02bef6fc..000000000 --- a/aci/login/cloud_environment.go +++ /dev/null @@ -1,274 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package login - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "os" - "strings" - - "github.com/pkg/errors" -) - -const ( - // AzurePublicCloudName is the moniker of the Azure public cloud - AzurePublicCloudName = "AzureCloud" - - // AcrSuffixKey is the well-known name of the DNS suffix for Azure Container Registries - AcrSuffixKey = "acrLoginServer" - - // CloudMetadataURLVar is the name of the environment variable that (if defined), points to a URL that should be used by Docker CLI to retrieve cloud metadata - CloudMetadataURLVar = "ARM_CLOUD_METADATA_URL" - - // DefaultCloudMetadataURL is the URL of the cloud metadata service maintained by Azure public cloud - DefaultCloudMetadataURL = "https://management.azure.com/metadata/endpoints?api-version=2019-05-01" -) - -// CloudEnvironmentService exposed metadata about Azure cloud environments -type CloudEnvironmentService interface { - Get(name string) (CloudEnvironment, error) -} - -type cloudEnvironmentService struct { - cloudEnvironments map[string]CloudEnvironment - cloudMetadataURL string - // True if we have queried the cloud metadata endpoint already. - // We do it only once per CLI invocation. - metadataQueried bool -} - -var ( - // CloudEnvironments is the default instance of the CloudEnvironmentService - CloudEnvironments CloudEnvironmentService -) - -func init() { - CloudEnvironments = newCloudEnvironmentService() -} - -// CloudEnvironmentAuthentication data for logging into, and obtaining tokens for, Azure sovereign clouds -type CloudEnvironmentAuthentication struct { - LoginEndpoint string `json:"loginEndpoint"` - Audiences []string `json:"audiences"` - Tenant string `json:"tenant"` -} - -// CloudEnvironment describes Azure sovereign cloud instance (e.g. Azure public, Azure US government, Azure China etc.) -type CloudEnvironment struct { - Name string `json:"name"` - Authentication CloudEnvironmentAuthentication `json:"authentication"` - ResourceManagerURL string `json:"resourceManager"` - Suffixes map[string]string `json:"suffixes"` -} - -func newCloudEnvironmentService() *cloudEnvironmentService { - retval := cloudEnvironmentService{ - metadataQueried: false, - } - retval.resetCloudMetadata() - return &retval -} - -func (ces *cloudEnvironmentService) Get(name string) (CloudEnvironment, error) { - if ce, present := ces.cloudEnvironments[name]; present { - return ce, nil - } - - if !ces.metadataQueried { - ces.metadataQueried = true - - if ces.cloudMetadataURL == "" { - ces.cloudMetadataURL = os.Getenv(CloudMetadataURLVar) - if _, err := url.ParseRequestURI(ces.cloudMetadataURL); err != nil { - ces.cloudMetadataURL = DefaultCloudMetadataURL - } - } - - res, err := http.Get(ces.cloudMetadataURL) - if err != nil { - return CloudEnvironment{}, fmt.Errorf("Cloud metadata retrieval from '%s' failed: %w", ces.cloudMetadataURL, err) - } - if res.StatusCode != 200 { - return CloudEnvironment{}, errors.Errorf("Cloud metadata retrieval from '%s' failed: server response was '%s'", ces.cloudMetadataURL, res.Status) - } - - bytes, err := ioutil.ReadAll(res.Body) - if err != nil { - return CloudEnvironment{}, fmt.Errorf("Cloud metadata retrieval from '%s' failed: %w", ces.cloudMetadataURL, err) - } - - if err = ces.applyCloudMetadata(bytes); err != nil { - return CloudEnvironment{}, fmt.Errorf("Cloud metadata retrieval from '%s' failed: %w", ces.cloudMetadataURL, err) - } - } - - if ce, present := ces.cloudEnvironments[name]; present { - return ce, nil - } - - return CloudEnvironment{}, errors.Errorf("Cloud environment '%s' was not found", name) -} - -func (ces *cloudEnvironmentService) applyCloudMetadata(jsonBytes []byte) error { - input := []CloudEnvironment{} - if err := json.Unmarshal(jsonBytes, &input); err != nil { - return err - } - - newEnvironments := make(map[string]CloudEnvironment, len(input)) - // If _any_ of the submitted data is invalid, we bail out. - for _, e := range input { - if len(e.Name) == 0 { - return errors.New("Azure cloud environment metadata is invalid (an environment with no name has been encountered)") - } - - e.normalizeURLs() - - if _, err := url.ParseRequestURI(e.Authentication.LoginEndpoint); err != nil { - return errors.Errorf("Metadata of cloud environment '%s' has invalid login endpoint URL: %s", e.Name, e.Authentication.LoginEndpoint) - } - - if _, err := url.ParseRequestURI(e.ResourceManagerURL); err != nil { - return errors.Errorf("Metadata of cloud environment '%s' has invalid resource manager URL: %s", e.Name, e.ResourceManagerURL) - } - - if len(e.Authentication.Audiences) == 0 { - return errors.Errorf("Metadata of cloud environment '%s' is invalid (no authentication audiences)", e.Name) - } - - newEnvironments[e.Name] = e - } - - for name, e := range newEnvironments { - ces.cloudEnvironments[name] = e - } - return nil -} - -func (ces *cloudEnvironmentService) resetCloudMetadata() { - azurePublicCloud := CloudEnvironment{ - Name: AzurePublicCloudName, - Authentication: CloudEnvironmentAuthentication{ - LoginEndpoint: "https://login.microsoftonline.com", - Audiences: []string{ - "https://management.core.windows.net", - "https://management.azure.com", - }, - Tenant: "common", - }, - ResourceManagerURL: "https://management.azure.com", - Suffixes: map[string]string{ - AcrSuffixKey: "azurecr.io", - }, - } - - azureChinaCloud := CloudEnvironment{ - Name: "AzureChinaCloud", - Authentication: CloudEnvironmentAuthentication{ - LoginEndpoint: "https://login.chinacloudapi.cn", - Audiences: []string{ - "https://management.core.chinacloudapi.cn", - "https://management.chinacloudapi.cn", - }, - Tenant: "common", - }, - ResourceManagerURL: "https://management.chinacloudapi.cn", - Suffixes: map[string]string{ - AcrSuffixKey: "azurecr.cn", - }, - } - - azureUSGovernment := CloudEnvironment{ - Name: "AzureUSGovernment", - Authentication: CloudEnvironmentAuthentication{ - LoginEndpoint: "https://login.microsoftonline.us", - Audiences: []string{ - "https://management.core.usgovcloudapi.net", - "https://management.usgovcloudapi.net", - }, - Tenant: "common", - }, - ResourceManagerURL: "https://management.usgovcloudapi.net", - Suffixes: map[string]string{ - AcrSuffixKey: "azurecr.us", - }, - } - - azureGermanCloud := CloudEnvironment{ - Name: "AzureGermanCloud", - Authentication: CloudEnvironmentAuthentication{ - LoginEndpoint: "https://login.microsoftonline.de", - Audiences: []string{ - "https://management.core.cloudapi.de", - "https://management.microsoftazure.de", - }, - Tenant: "common", - }, - ResourceManagerURL: "https://management.microsoftazure.de", - - // There is no separate container registry suffix for German cloud - Suffixes: map[string]string{}, - } - - ces.cloudEnvironments = map[string]CloudEnvironment{ - azurePublicCloud.Name: azurePublicCloud, - azureChinaCloud.Name: azureChinaCloud, - azureUSGovernment.Name: azureUSGovernment, - azureGermanCloud.Name: azureGermanCloud, - } -} - -// GetTenantQueryURL returns an URL that can be used to fetch the list of Azure Active Directory tenants from a given cloud environment -func (ce *CloudEnvironment) GetTenantQueryURL() string { - tenantURL := ce.ResourceManagerURL + "/tenants?api-version=2019-11-01" - return tenantURL -} - -// GetTokenScope returns a token scope that fits Docker CLI Azure management API usage -func (ce *CloudEnvironment) GetTokenScope() string { - scope := "offline_access " + ce.ResourceManagerURL + "/.default" - return scope -} - -// GetAuthorizeRequestFormat returns a string format that can be used to construct authorization code request in an OAuth2 flow. -// The URL uses login endpoint appropriate for given cloud environment. -func (ce *CloudEnvironment) GetAuthorizeRequestFormat() string { - authorizeFormat := ce.Authentication.LoginEndpoint + "/organizations/oauth2/v2.0/authorize?response_type=code&client_id=%s&redirect_uri=%s&state=%s&prompt=select_account&response_mode=query&scope=%s" - return authorizeFormat -} - -// GetTokenRequestFormat returns a string format that can be used to construct a security token request against Azure Active Directory -func (ce *CloudEnvironment) GetTokenRequestFormat() string { - tokenEndpoint := ce.Authentication.LoginEndpoint + "/%s/oauth2/v2.0/token" - return tokenEndpoint -} - -func (ce *CloudEnvironment) normalizeURLs() { - ce.ResourceManagerURL = removeTrailingSlash(ce.ResourceManagerURL) - ce.Authentication.LoginEndpoint = removeTrailingSlash(ce.Authentication.LoginEndpoint) - for i, s := range ce.Authentication.Audiences { - ce.Authentication.Audiences[i] = removeTrailingSlash(s) - } -} - -func removeTrailingSlash(s string) string { - return strings.TrimSuffix(s, "/") -} diff --git a/aci/login/cloud_environment_test.go b/aci/login/cloud_environment_test.go deleted file mode 100644 index 57577761a..000000000 --- a/aci/login/cloud_environment_test.go +++ /dev/null @@ -1,187 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package login - -import ( - "testing" - - "gotest.tools/v3/assert" -) - -func TestNormalizeCloudEnvironmentURLs(t *testing.T) { - ce := CloudEnvironment{ - Name: "SecretCloud", - Authentication: CloudEnvironmentAuthentication{ - LoginEndpoint: "https://login.here.com/", - Audiences: []string{ - "https://audience1", - "https://audience2/", - }, - Tenant: "common", - }, - ResourceManagerURL: "invalid URL", - } - - ce.normalizeURLs() - - assert.Equal(t, ce.Authentication.LoginEndpoint, "https://login.here.com") - assert.Equal(t, ce.Authentication.Audiences[0], "https://audience1") - assert.Equal(t, ce.Authentication.Audiences[1], "https://audience2") -} - -func TestApplyInvalidCloudMetadataJSON(t *testing.T) { - ce := newCloudEnvironmentService() - bb := []byte(`This isn't really valid JSON`) - - err := ce.applyCloudMetadata(bb) - - assert.Assert(t, err != nil, "Cloud metadata was invalid, so the application should have failed") - ensureWellKnownCloudMetadata(t, ce) -} - -func TestApplyInvalidCloudMetatada(t *testing.T) { - ce := newCloudEnvironmentService() - - // No name (moniker) for the cloud - bb := []byte(` - [{ - "authentication": { - "loginEndpoint": "https://login.docker.com/", - "audiences": [ - "https://management.docker.com/", - "https://management.cli.docker.com/" - ], - "tenant": "F5773994-FE88-482E-9E33-6E799D250416" - }, - "suffixes": { - "acrLoginServer": "azurecr.docker.io" - }, - "resourceManager": "https://management.docker.com/" - }]`) - - err := ce.applyCloudMetadata(bb) - assert.ErrorContains(t, err, "no name") - ensureWellKnownCloudMetadata(t, ce) - - // Invalid resource manager URL - bb = []byte(` - [{ - "authentication": { - "loginEndpoint": "https://login.docker.com/", - "audiences": [ - "https://management.docker.com/", - "https://management.cli.docker.com/" - ], - "tenant": "F5773994-FE88-482E-9E33-6E799D250416" - }, - "name": "DockerAzureCloud", - "suffixes": { - "acrLoginServer": "azurecr.docker.io" - }, - "resourceManager": "123" - }]`) - - err = ce.applyCloudMetadata(bb) - assert.ErrorContains(t, err, "invalid resource manager URL") - ensureWellKnownCloudMetadata(t, ce) - - // Invalid login endpoint - bb = []byte(` - [{ - "authentication": { - "loginEndpoint": "456", - "audiences": [ - "https://management.docker.com/", - "https://management.cli.docker.com/" - ], - "tenant": "F5773994-FE88-482E-9E33-6E799D250416" - }, - "name": "DockerAzureCloud", - "suffixes": { - "acrLoginServer": "azurecr.docker.io" - }, - "resourceManager": "https://management.docker.com/" - }]`) - - err = ce.applyCloudMetadata(bb) - assert.ErrorContains(t, err, "invalid login endpoint") - ensureWellKnownCloudMetadata(t, ce) - - // No audiences - bb = []byte(` - [{ - "authentication": { - "loginEndpoint": "https://login.docker.com/", - "audiences": [ ], - "tenant": "F5773994-FE88-482E-9E33-6E799D250416" - }, - "name": "DockerAzureCloud", - "suffixes": { - "acrLoginServer": "azurecr.docker.io" - }, - "resourceManager": "https://management.docker.com/" - }]`) - - err = ce.applyCloudMetadata(bb) - assert.ErrorContains(t, err, "no authentication audiences") - ensureWellKnownCloudMetadata(t, ce) -} - -func TestApplyCloudMetadata(t *testing.T) { - ce := newCloudEnvironmentService() - - bb := []byte(` - [{ - "authentication": { - "loginEndpoint": "https://login.docker.com/", - "audiences": [ - "https://management.docker.com/", - "https://management.cli.docker.com/" - ], - "tenant": "F5773994-FE88-482E-9E33-6E799D250416" - }, - "name": "DockerAzureCloud", - "suffixes": { - "acrLoginServer": "azurecr.docker.io" - }, - "resourceManager": "https://management.docker.com/" - }]`) - - err := ce.applyCloudMetadata(bb) - assert.NilError(t, err) - - env, err := ce.Get("DockerAzureCloud") - assert.NilError(t, err) - assert.Equal(t, env.Authentication.LoginEndpoint, "https://login.docker.com") - ensureWellKnownCloudMetadata(t, ce) -} - -func TestDefaultCloudMetadataPresent(t *testing.T) { - ensureWellKnownCloudMetadata(t, CloudEnvironments) -} - -func ensureWellKnownCloudMetadata(t *testing.T, ce CloudEnvironmentService) { - // Make sure well-known public cloud information is still available - _, err := ce.Get(AzurePublicCloudName) - assert.NilError(t, err) - - _, err = ce.Get("AzureChinaCloud") - assert.NilError(t, err) - - _, err = ce.Get("AzureUSGovernment") - assert.NilError(t, err) -} diff --git a/aci/login/helper.go b/aci/login/helper.go deleted file mode 100644 index dd44b11e7..000000000 --- a/aci/login/helper.go +++ /dev/null @@ -1,134 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package login - -import ( - "context" - "encoding/json" - "fmt" - "io/ioutil" - "math/rand" - "net/http" - "net/url" - "os/exec" - "runtime" - "strings" - - "github.com/Azure/go-autorest/autorest/adal" - "github.com/Azure/go-autorest/autorest/azure/auth" - - "github.com/pkg/errors" -) - -var ( - letterRunes = []rune("abcdefghijklmnopqrstuvwxyz123456789") -) - -type apiHelper interface { - queryToken(ce CloudEnvironment, data url.Values, tenantID string) (azureToken, error) - openAzureLoginPage(redirectURL string, ce CloudEnvironment) error - queryAPIWithHeader(ctx context.Context, authorizationURL string, authorizationHeader string) ([]byte, int, error) - getDeviceCodeFlowToken(ce CloudEnvironment) (adal.Token, error) -} - -type azureAPIHelper struct{} - -func (helper azureAPIHelper) getDeviceCodeFlowToken(ce CloudEnvironment) (adal.Token, error) { - deviceconfig := auth.NewDeviceFlowConfig(clientID, "common") - deviceconfig.Resource = ce.ResourceManagerURL - spToken, err := deviceconfig.ServicePrincipalToken() - if err != nil { - return adal.Token{}, err - } - return spToken.Token(), err -} - -func (helper azureAPIHelper) openAzureLoginPage(redirectURL string, ce CloudEnvironment) error { - state := randomString("", 10) - authURL := fmt.Sprintf(ce.GetAuthorizeRequestFormat(), clientID, redirectURL, state, ce.GetTokenScope()) - return openbrowser(authURL) -} - -func (helper azureAPIHelper) queryAPIWithHeader(ctx context.Context, authorizationURL string, authorizationHeader string) ([]byte, int, error) { - req, err := http.NewRequest(http.MethodGet, authorizationURL, nil) - if err != nil { - return nil, 0, err - } - req = req.WithContext(ctx) - req.Header.Add("Authorization", authorizationHeader) - res, err := http.DefaultClient.Do(req) - if err != nil { - return nil, 0, err - } - bits, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, 0, err - } - return bits, res.StatusCode, nil -} - -func (helper azureAPIHelper) queryToken(ce CloudEnvironment, data url.Values, tenantID string) (azureToken, error) { - res, err := http.Post(fmt.Sprintf(ce.GetTokenRequestFormat(), tenantID), "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) - if err != nil { - return azureToken{}, err - } - if res.StatusCode != 200 { - return azureToken{}, errors.Errorf("error while renewing access token, status : %s", res.Status) - } - bits, err := ioutil.ReadAll(res.Body) - if err != nil { - return azureToken{}, err - } - token := azureToken{} - if err := json.Unmarshal(bits, &token); err != nil { - return azureToken{}, err - } - return token, nil -} - -func openbrowser(address string) error { - switch runtime.GOOS { - case "linux": - if isWsl() { - return exec.Command("wslview", address).Run() - } - return exec.Command("xdg-open", address).Run() - case "windows": - return exec.Command("rundll32", "url.dll,FileProtocolHandler", address).Run() - case "darwin": - return exec.Command("open", address).Run() - default: - return fmt.Errorf("unsupported platform") - } -} - -func isWsl() bool { - b, err := ioutil.ReadFile("/proc/version") - if err != nil { - return false - } - - return strings.Contains(strings.ToLower(string(b)), "microsoft") -} - -func randomString(prefix string, length int) string { - b := make([]rune, length) - for i := range b { - b[i] = letterRunes[rand.Intn(len(letterRunes))] - } - return prefix + string(b) -} diff --git a/aci/login/local_server.go b/aci/login/local_server.go deleted file mode 100644 index f65fa3e04..000000000 --- a/aci/login/local_server.go +++ /dev/null @@ -1,135 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package login - -import ( - "fmt" - "net" - "net/http" - "net/url" - - "github.com/pkg/errors" -) - -const failHTML = ` - - - - - Login failed - - -

Some failures occurred during the authentication

-

You can log an issue at Azure CLI GitHub Repository and we will assist you in resolving it.

- - -` - -const successHTML = ` - - - - - - Login successfully - - -

You have logged into Microsoft Azure!

-

You can close this window, or we will redirect you to the Docker ACI integration documentation in 10 seconds.

- - -` - -const ( - // redirectHost is where the user's browser is redirected on authentication - // completion. Note that only registered hosts can be used. i.e.: - // "localhost" works but "127.0.0.1" does not. - redirectHost = "localhost" -) - -type localResponse struct { - values url.Values - err error -} - -// LocalServer is an Azure login server -type LocalServer struct { - httpServer *http.Server - listener net.Listener - queryCh chan localResponse -} - -// NewLocalServer creates an Azure login server -func NewLocalServer(queryCh chan localResponse) (*LocalServer, error) { - s := &LocalServer{queryCh: queryCh} - mux := http.NewServeMux() - mux.HandleFunc("/", s.handler()) - s.httpServer = &http.Server{Handler: mux} - l, err := net.Listen("tcp", redirectHost+":0") - if err != nil { - return nil, err - } - s.listener = l - p := l.Addr().(*net.TCPAddr).Port - if p == 0 { - return nil, errors.New("unable to allocate login server port") - } - return s, nil -} - -// Serve starts the local Azure login server -func (s *LocalServer) Serve() { - go func() { - if err := s.httpServer.Serve(s.listener); err != nil { - s.queryCh <- localResponse{ - err: errors.Wrap(err, "unable to start login server"), - } - } - }() -} - -// Close stops the local Azure login server -func (s *LocalServer) Close() { - _ = s.httpServer.Close() -} - -// Addr returns the address that the local Azure server is service to -func (s *LocalServer) Addr() string { - return fmt.Sprintf("http://%s:%d", redirectHost, s.listener.Addr().(*net.TCPAddr).Port) -} - -func (s *LocalServer) handler() func(w http.ResponseWriter, r *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - if _, hasCode := r.URL.Query()["code"]; hasCode { - if _, err := w.Write([]byte(successHTML)); err != nil { - s.queryCh <- localResponse{ - err: errors.Wrap(err, "unable to write success HTML"), - } - } else { - s.queryCh <- localResponse{values: r.URL.Query()} - } - } else { - if _, err := w.Write([]byte(failHTML)); err != nil { - s.queryCh <- localResponse{ - err: errors.Wrap(err, "unable to write fail HTML"), - } - } else { - s.queryCh <- localResponse{values: r.URL.Query()} - } - } - } -} diff --git a/aci/login/login.go b/aci/login/login.go deleted file mode 100644 index a4dffce1d..000000000 --- a/aci/login/login.go +++ /dev/null @@ -1,334 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package login - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - "os" - "time" - - "github.com/docker/compose-cli/pkg/api" - - "github.com/Azure/go-autorest/autorest/adal" - "github.com/Azure/go-autorest/autorest/azure/auth" - "github.com/pkg/errors" - "golang.org/x/oauth2" -) - -//go login process, derived from code sample provided by MS at https://github.com/devigned/go-az-cli-stuff -const ( - clientID = "04b07795-8ddb-461a-bbee-02f9e1bf7b46" // Azure CLI client id -) - -type ( - azureToken struct { - Type string `json:"token_type"` - Scope string `json:"scope"` - ExpiresIn int `json:"expires_in"` - ExtExpiresIn int `json:"ext_expires_in"` - AccessToken string `json:"access_token"` - RefreshToken string `json:"refresh_token"` - Foci string `json:"foci"` - } - - tenantResult struct { - Value []tenantValue `json:"value"` - } - tenantValue struct { - TenantID string `json:"tenantId"` - } -) - -// AzureLoginService Service to log into azure and get authentifier for azure APIs -type AzureLoginService interface { - Login(ctx context.Context, requestedTenantID string, cloudEnvironment string) error - LoginServicePrincipal(clientID string, clientSecret string, tenantID string, cloudEnvironment string) error - Logout(ctx context.Context) error - GetCloudEnvironment() (CloudEnvironment, error) - GetValidToken() (oauth2.Token, string, error) -} -type azureLoginService struct { - tokenStore tokenStore - apiHelper apiHelper - cloudEnvironmentSvc CloudEnvironmentService -} - -const tokenStoreFilename = "dockerAccessToken.json" - -// NewAzureLoginService creates a NewAzureLoginService -func NewAzureLoginService() (AzureLoginService, error) { - return newAzureLoginServiceFromPath(GetTokenStorePath(), azureAPIHelper{}, CloudEnvironments) -} - -func newAzureLoginServiceFromPath(tokenStorePath string, helper apiHelper, ces CloudEnvironmentService) (*azureLoginService, error) { - store, err := newTokenStore(tokenStorePath) - if err != nil { - return nil, err - } - return &azureLoginService{ - tokenStore: store, - apiHelper: helper, - cloudEnvironmentSvc: ces, - }, nil -} - -// LoginServicePrincipal login with clientId / clientSecret from a service principal. -// The resulting token does not include a refresh token -func (login *azureLoginService) LoginServicePrincipal(clientID string, clientSecret string, tenantID string, cloudEnvironment string) error { - // Tried with auth2.NewUsernamePasswordConfig() but could not make this work with username / password, setting this for CI with clientID / clientSecret - creds := auth.NewClientCredentialsConfig(clientID, clientSecret, tenantID) - - spToken, err := creds.ServicePrincipalToken() - if err != nil { - return errors.Wrapf(api.ErrLoginFailed, "could not login with service principal: %s", err) - } - err = spToken.Refresh() - if err != nil { - return errors.Wrapf(api.ErrLoginFailed, "could not login with service principal: %s", err) - } - token, err := spToOAuthToken(spToken.Token()) - if err != nil { - return errors.Wrapf(api.ErrLoginFailed, "could not read service principal token expiry: %s", err) - } - loginInfo := TokenInfo{TenantID: tenantID, Token: token, CloudEnvironment: cloudEnvironment} - - if err := login.tokenStore.writeLoginInfo(loginInfo); err != nil { - return errors.Wrapf(api.ErrLoginFailed, "could not store login info: %s", err) - } - return nil -} - -// Logout remove azure token data -func (login *azureLoginService) Logout(ctx context.Context) error { - err := login.tokenStore.removeData() - if os.IsNotExist(err) { - return errors.New("No Azure login data to be removed") - } - return err -} - -func (login *azureLoginService) getTenantAndValidateLogin( - ctx context.Context, - accessToken string, - refreshToken string, - requestedTenantID string, - ce CloudEnvironment, -) error { - bits, statusCode, err := login.apiHelper.queryAPIWithHeader(ctx, ce.GetTenantQueryURL(), fmt.Sprintf("Bearer %s", accessToken)) - if err != nil { - return errors.Wrapf(api.ErrLoginFailed, "check auth failed: %s", err) - } - - if statusCode != http.StatusOK { - return errors.Wrapf(api.ErrLoginFailed, "unable to login status code %d: %s", statusCode, bits) - } - var t tenantResult - if err := json.Unmarshal(bits, &t); err != nil { - return errors.Wrapf(api.ErrLoginFailed, "unable to unmarshal tenant: %s", err) - } - tenantID, err := getTenantID(t.Value, requestedTenantID) - if err != nil { - return errors.Wrap(api.ErrLoginFailed, err.Error()) - } - tToken, err := login.refreshToken(refreshToken, tenantID, ce) - if err != nil { - return errors.Wrapf(api.ErrLoginFailed, "unable to refresh token: %s", err) - } - loginInfo := TokenInfo{TenantID: tenantID, Token: tToken, CloudEnvironment: ce.Name} - - if err := login.tokenStore.writeLoginInfo(loginInfo); err != nil { - return errors.Wrapf(api.ErrLoginFailed, "could not store login info: %s", err) - } - return nil -} - -// Login performs an Azure login through a web browser -func (login *azureLoginService) Login(ctx context.Context, requestedTenantID string, cloudEnvironment string) error { - ce, err := login.cloudEnvironmentSvc.Get(cloudEnvironment) - if err != nil { - return err - } - - queryCh := make(chan localResponse, 1) - s, err := NewLocalServer(queryCh) - if err != nil { - return err - } - s.Serve() - defer s.Close() - - redirectURL := s.Addr() - if redirectURL == "" { - return errors.Wrap(api.ErrLoginFailed, "empty redirect URL") - } - - deviceCodeFlowCh := make(chan deviceCodeFlowResponse, 1) - if err = login.apiHelper.openAzureLoginPage(redirectURL, ce); err != nil { - login.startDeviceCodeFlow(deviceCodeFlowCh, ce) - } - - select { - case <-ctx.Done(): - return ctx.Err() - case dcft := <-deviceCodeFlowCh: - if dcft.err != nil { - return errors.Wrapf(api.ErrLoginFailed, "could not get token using device code flow: %s", err) - } - token := dcft.token - return login.getTenantAndValidateLogin(ctx, token.AccessToken, token.RefreshToken, requestedTenantID, ce) - case q := <-queryCh: - if q.err != nil { - return errors.Wrapf(api.ErrLoginFailed, "unhandled local login server error: %s", err) - } - code, hasCode := q.values["code"] - if !hasCode { - return errors.Wrap(api.ErrLoginFailed, "no login code") - } - data := url.Values{ - "grant_type": []string{"authorization_code"}, - "client_id": []string{clientID}, - "code": code, - "scope": []string{ce.GetTokenScope()}, - "redirect_uri": []string{redirectURL}, - } - token, err := login.apiHelper.queryToken(ce, data, "organizations") - if err != nil { - return errors.Wrapf(api.ErrLoginFailed, "access token request failed: %s", err) - } - return login.getTenantAndValidateLogin(ctx, token.AccessToken, token.RefreshToken, requestedTenantID, ce) - } -} - -type deviceCodeFlowResponse struct { - token adal.Token - err error -} - -func (login *azureLoginService) startDeviceCodeFlow(deviceCodeFlowCh chan deviceCodeFlowResponse, ce CloudEnvironment) { - fmt.Println("Could not automatically open a browser, falling back to Azure device code flow authentication") - go func() { - token, err := login.apiHelper.getDeviceCodeFlowToken(ce) - if err != nil { - deviceCodeFlowCh <- deviceCodeFlowResponse{err: err} - } - deviceCodeFlowCh <- deviceCodeFlowResponse{token: token} - }() -} - -func getTenantID(tenantValues []tenantValue, requestedTenantID string) (string, error) { - if requestedTenantID == "" { - if len(tenantValues) < 1 { - return "", errors.Errorf("could not find azure tenant") - } - return tenantValues[0].TenantID, nil - } - for _, tValue := range tenantValues { - if tValue.TenantID == requestedTenantID { - return tValue.TenantID, nil - } - } - return "", errors.Errorf("could not find requested azure tenant %s", requestedTenantID) -} - -func toOAuthToken(token azureToken) oauth2.Token { - expireTime := time.Now().Add(time.Duration(token.ExpiresIn) * time.Second) - oauthToken := oauth2.Token{ - RefreshToken: token.RefreshToken, - AccessToken: token.AccessToken, - Expiry: expireTime, - TokenType: token.Type, - } - return oauthToken -} - -func spToOAuthToken(token adal.Token) (oauth2.Token, error) { - expiresIn, err := token.ExpiresIn.Int64() - if err != nil { - return oauth2.Token{}, err - } - expireTime := time.Now().Add(time.Duration(expiresIn) * time.Second) - oauthToken := oauth2.Token{ - RefreshToken: token.RefreshToken, - AccessToken: token.AccessToken, - Expiry: expireTime, - TokenType: token.Type, - } - return oauthToken, nil -} - -// GetValidToken returns an access token and associated tenant ID. -// Will refresh the token as necessary. -func (login *azureLoginService) GetValidToken() (oauth2.Token, string, error) { - loginInfo, err := login.tokenStore.readToken() - if err != nil { - return oauth2.Token{}, "", err - } - token := loginInfo.Token - tenantID := loginInfo.TenantID - if token.Valid() { - return token, tenantID, nil - } - - ce, err := login.cloudEnvironmentSvc.Get(loginInfo.CloudEnvironment) - if err != nil { - return oauth2.Token{}, "", errors.Wrap(err, "access token request failed--cloud environment could not be determined.") - } - - token, err = login.refreshToken(token.RefreshToken, tenantID, ce) - if err != nil { - return oauth2.Token{}, "", errors.Wrap(err, "access token request failed. Maybe you need to login to Azure again.") - } - err = login.tokenStore.writeLoginInfo(TokenInfo{TenantID: tenantID, Token: token, CloudEnvironment: ce.Name}) - if err != nil { - return oauth2.Token{}, "", err - } - return token, tenantID, nil -} - -// GeCloudEnvironment returns the cloud environment associated with the current authentication token (if we have one) -func (login *azureLoginService) GetCloudEnvironment() (CloudEnvironment, error) { - tokenInfo, err := login.tokenStore.readToken() - if err != nil { - return CloudEnvironment{}, err - } - - cloudEnvironment, err := login.cloudEnvironmentSvc.Get(tokenInfo.CloudEnvironment) - if err != nil { - return CloudEnvironment{}, err - } - - return cloudEnvironment, nil -} - -func (login *azureLoginService) refreshToken(currentRefreshToken string, tenantID string, ce CloudEnvironment) (oauth2.Token, error) { - data := url.Values{ - "grant_type": []string{"refresh_token"}, - "client_id": []string{clientID}, - "scope": []string{ce.GetTokenScope()}, - "refresh_token": []string{currentRefreshToken}, - } - token, err := login.apiHelper.queryToken(ce, data, tenantID) - if err != nil { - return oauth2.Token{}, err - } - - return toOAuthToken(token), nil -} diff --git a/aci/login/login_test.go b/aci/login/login_test.go deleted file mode 100644 index 3388945a1..000000000 --- a/aci/login/login_test.go +++ /dev/null @@ -1,594 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package login - -import ( - "context" - "errors" - "io/ioutil" - "net/http" - "net/http/httptest" - "net/url" - "os" - "path/filepath" - "reflect" - "sync/atomic" - "testing" - "time" - - "github.com/Azure/go-autorest/autorest/adal" - - "github.com/stretchr/testify/mock" - "gotest.tools/v3/assert" - - "golang.org/x/oauth2" -) - -func testLoginService(t *testing.T, apiHelperMock *MockAzureHelper, cloudEnvironmentSvc CloudEnvironmentService) (*azureLoginService, error) { - dir, err := ioutil.TempDir("", "test_store") - if err != nil { - return nil, err - } - t.Cleanup(func() { - _ = os.RemoveAll(dir) - }) - - ces := CloudEnvironments - if cloudEnvironmentSvc != nil { - ces = cloudEnvironmentSvc - } - return newAzureLoginServiceFromPath(filepath.Join(dir, tokenStoreFilename), apiHelperMock, ces) -} - -func TestRefreshInValidToken(t *testing.T) { - data := url.Values{ - "grant_type": []string{"refresh_token"}, - "client_id": []string{clientID}, - "scope": []string{"offline_access https://management.docker.com/.default"}, - "refresh_token": []string{"refreshToken"}, - } - helperMock := &MockAzureHelper{} - helperMock.On("queryToken", mock.AnythingOfType("login.CloudEnvironment"), data, "123456").Return(azureToken{ - RefreshToken: "newRefreshToken", - AccessToken: "newAccessToken", - ExpiresIn: 3600, - Foci: "1", - }, nil) - - cloudEnvironmentSvcMock := &MockCloudEnvironmentService{} - cloudEnvironmentSvcMock.On("Get", "AzureDockerCloud").Return(CloudEnvironment{ - Name: "AzureDockerCloud", - Authentication: CloudEnvironmentAuthentication{ - LoginEndpoint: "https://login.docker.com", - Audiences: []string{ - "https://management.docker.com", - "https://management-ext.docker.com", - }, - Tenant: "common", - }, - ResourceManagerURL: "https://management.docker.com", - Suffixes: map[string]string{}, - }, nil) - - azureLogin, err := testLoginService(t, helperMock, cloudEnvironmentSvcMock) - assert.NilError(t, err) - err = azureLogin.tokenStore.writeLoginInfo(TokenInfo{ - TenantID: "123456", - Token: oauth2.Token{ - AccessToken: "accessToken", - RefreshToken: "refreshToken", - Expiry: time.Now().Add(-1 * time.Hour), - TokenType: "Bearer", - }, - CloudEnvironment: "AzureDockerCloud", - }) - assert.NilError(t, err) - - token, tenantID, err := azureLogin.GetValidToken() - assert.NilError(t, err) - assert.Equal(t, tenantID, "123456") - - assert.Equal(t, token.AccessToken, "newAccessToken") - assert.Assert(t, time.Now().Add(3500*time.Second).Before(token.Expiry)) - - storedToken, err := azureLogin.tokenStore.readToken() - assert.NilError(t, err) - assert.Equal(t, storedToken.Token.AccessToken, "newAccessToken") - assert.Equal(t, storedToken.Token.RefreshToken, "newRefreshToken") - assert.Assert(t, time.Now().Add(3500*time.Second).Before(storedToken.Token.Expiry)) - - assert.Equal(t, storedToken.CloudEnvironment, "AzureDockerCloud") -} - -func TestDoesNotRefreshValidToken(t *testing.T) { - expiryDate := time.Now().Add(1 * time.Hour) - azureLogin, err := testLoginService(t, nil, nil) - assert.NilError(t, err) - err = azureLogin.tokenStore.writeLoginInfo(TokenInfo{ - TenantID: "123456", - Token: oauth2.Token{ - AccessToken: "accessToken", - RefreshToken: "refreshToken", - Expiry: expiryDate, - TokenType: "Bearer", - }, - CloudEnvironment: AzurePublicCloudName, - }) - assert.NilError(t, err) - - token, tenantID, err := azureLogin.GetValidToken() - assert.NilError(t, err) - assert.Equal(t, token.AccessToken, "accessToken") - assert.Equal(t, tenantID, "123456") -} - -func TestTokenStoreAssumesAzurePublicCloud(t *testing.T) { - expiryDate := time.Now().Add(1 * time.Hour) - azureLogin, err := testLoginService(t, nil, nil) - assert.NilError(t, err) - err = azureLogin.tokenStore.writeLoginInfo(TokenInfo{ - TenantID: "123456", - Token: oauth2.Token{ - AccessToken: "accessToken", - RefreshToken: "refreshToken", - Expiry: expiryDate, - TokenType: "Bearer", - }, - // Simulates upgrade from older version of Docker CLI that did not have cloud environment concept - CloudEnvironment: "", - }) - assert.NilError(t, err) - - token, tenantID, err := azureLogin.GetValidToken() - assert.NilError(t, err) - assert.Equal(t, tenantID, "123456") - assert.Equal(t, token.AccessToken, "accessToken") - - ce, err := azureLogin.GetCloudEnvironment() - assert.NilError(t, err) - assert.Equal(t, ce.Name, AzurePublicCloudName) -} - -func TestInvalidLogin(t *testing.T) { - m := &MockAzureHelper{} - m.On("openAzureLoginPage", mock.AnythingOfType("string"), mock.AnythingOfType("CloudEnvironment")).Run(func(args mock.Arguments) { - redirectURL := args.Get(0).(string) - err := queryKeyValue(redirectURL, "error", "access denied: login failed") - assert.NilError(t, err) - }).Return(nil) - - azureLogin, err := testLoginService(t, m, nil) - assert.NilError(t, err) - - err = azureLogin.Login(context.TODO(), "", AzurePublicCloudName) - assert.Error(t, err, "no login code: login failed") -} - -func TestValidLogin(t *testing.T) { - var redirectURL string - ctx := context.TODO() - m := &MockAzureHelper{} - ce, err := CloudEnvironments.Get(AzurePublicCloudName) - assert.NilError(t, err) - - m.On("openAzureLoginPage", mock.AnythingOfType("string"), mock.AnythingOfType("CloudEnvironment")).Run(func(args mock.Arguments) { - redirectURL = args.Get(0).(string) - err := queryKeyValue(redirectURL, "code", "123456879") - assert.NilError(t, err) - }).Return(nil) - - m.On("queryToken", mock.AnythingOfType("login.CloudEnvironment"), mock.MatchedBy(func(data url.Values) bool { - //Need a matcher here because the value of redirectUrl is not known until executing openAzureLoginPage - return reflect.DeepEqual(data, url.Values{ - "grant_type": []string{"authorization_code"}, - "client_id": []string{clientID}, - "code": []string{"123456879"}, - "scope": []string{ce.GetTokenScope()}, - "redirect_uri": []string{redirectURL}, - }) - }), "organizations").Return(azureToken{ - RefreshToken: "firstRefreshToken", - AccessToken: "firstAccessToken", - ExpiresIn: 3600, - Foci: "1", - }, nil) - - authBody := `{"value":[{"id":"/tenants/12345a7c-c56d-43e8-9549-dd230ce8a038","tenantId":"12345a7c-c56d-43e8-9549-dd230ce8a038"}]}` - - m.On("queryAPIWithHeader", ctx, ce.GetTenantQueryURL(), "Bearer firstAccessToken").Return([]byte(authBody), 200, nil) - data := refreshTokenData("firstRefreshToken", ce) - m.On("queryToken", mock.AnythingOfType("login.CloudEnvironment"), data, "12345a7c-c56d-43e8-9549-dd230ce8a038").Return(azureToken{ - RefreshToken: "newRefreshToken", - AccessToken: "newAccessToken", - ExpiresIn: 3600, - Foci: "1", - }, nil) - azureLogin, err := testLoginService(t, m, nil) - assert.NilError(t, err) - - err = azureLogin.Login(ctx, "", AzurePublicCloudName) - assert.NilError(t, err) - - loginToken, err := azureLogin.tokenStore.readToken() - assert.NilError(t, err) - assert.Equal(t, loginToken.Token.AccessToken, "newAccessToken") - assert.Equal(t, loginToken.Token.RefreshToken, "newRefreshToken") - assert.Assert(t, time.Now().Add(3500*time.Second).Before(loginToken.Token.Expiry)) - assert.Equal(t, loginToken.TenantID, "12345a7c-c56d-43e8-9549-dd230ce8a038") - assert.Equal(t, loginToken.Token.Type(), "Bearer") - assert.Equal(t, loginToken.CloudEnvironment, "AzureCloud") -} - -func TestValidLoginRequestedTenant(t *testing.T) { - var redirectURL string - m := &MockAzureHelper{} - ce, err := CloudEnvironments.Get(AzurePublicCloudName) - assert.NilError(t, err) - - m.On("openAzureLoginPage", mock.AnythingOfType("string"), mock.AnythingOfType("CloudEnvironment")).Run(func(args mock.Arguments) { - redirectURL = args.Get(0).(string) - err := queryKeyValue(redirectURL, "code", "123456879") - assert.NilError(t, err) - }).Return(nil) - - m.On("queryToken", mock.AnythingOfType("login.CloudEnvironment"), mock.MatchedBy(func(data url.Values) bool { - //Need a matcher here because the value of redirectUrl is not known until executing openAzureLoginPage - return reflect.DeepEqual(data, url.Values{ - "grant_type": []string{"authorization_code"}, - "client_id": []string{clientID}, - "code": []string{"123456879"}, - "scope": []string{ce.GetTokenScope()}, - "redirect_uri": []string{redirectURL}, - }) - }), "organizations").Return(azureToken{ - RefreshToken: "firstRefreshToken", - AccessToken: "firstAccessToken", - ExpiresIn: 3600, - Foci: "1", - }, nil) - - authBody := `{"value":[{"id":"/tenants/00000000-c56d-43e8-9549-dd230ce8a038","tenantId":"00000000-c56d-43e8-9549-dd230ce8a038"}, - {"id":"/tenants/12345a7c-c56d-43e8-9549-dd230ce8a038","tenantId":"12345a7c-c56d-43e8-9549-dd230ce8a038"}]}` - - ctx := context.TODO() - m.On("queryAPIWithHeader", ctx, ce.GetTenantQueryURL(), "Bearer firstAccessToken").Return([]byte(authBody), 200, nil) - data := refreshTokenData("firstRefreshToken", ce) - m.On("queryToken", mock.AnythingOfType("login.CloudEnvironment"), data, "12345a7c-c56d-43e8-9549-dd230ce8a038").Return(azureToken{ - RefreshToken: "newRefreshToken", - AccessToken: "newAccessToken", - ExpiresIn: 3600, - Foci: "1", - }, nil) - azureLogin, err := testLoginService(t, m, nil) - assert.NilError(t, err) - - err = azureLogin.Login(ctx, "12345a7c-c56d-43e8-9549-dd230ce8a038", AzurePublicCloudName) - assert.NilError(t, err) - - loginToken, err := azureLogin.tokenStore.readToken() - assert.NilError(t, err) - assert.Equal(t, loginToken.Token.AccessToken, "newAccessToken") - assert.Equal(t, loginToken.Token.RefreshToken, "newRefreshToken") - assert.Assert(t, time.Now().Add(3500*time.Second).Before(loginToken.Token.Expiry)) - assert.Equal(t, loginToken.TenantID, "12345a7c-c56d-43e8-9549-dd230ce8a038") - assert.Equal(t, loginToken.Token.Type(), "Bearer") - assert.Equal(t, loginToken.CloudEnvironment, "AzureCloud") -} - -func TestLoginNoTenant(t *testing.T) { - var redirectURL string - m := &MockAzureHelper{} - ce, err := CloudEnvironments.Get(AzurePublicCloudName) - assert.NilError(t, err) - - m.On("openAzureLoginPage", mock.AnythingOfType("string"), mock.AnythingOfType("CloudEnvironment")).Run(func(args mock.Arguments) { - redirectURL = args.Get(0).(string) - err := queryKeyValue(redirectURL, "code", "123456879") - assert.NilError(t, err) - }).Return(nil) - - m.On("queryToken", mock.AnythingOfType("login.CloudEnvironment"), mock.MatchedBy(func(data url.Values) bool { - //Need a matcher here because the value of redirectUrl is not known until executing openAzureLoginPage - return reflect.DeepEqual(data, url.Values{ - "grant_type": []string{"authorization_code"}, - "client_id": []string{clientID}, - "code": []string{"123456879"}, - "scope": []string{ce.GetTokenScope()}, - "redirect_uri": []string{redirectURL}, - }) - }), "organizations").Return(azureToken{ - RefreshToken: "firstRefreshToken", - AccessToken: "firstAccessToken", - ExpiresIn: 3600, - Foci: "1", - }, nil) - - ctx := context.TODO() - authBody := `{"value":[{"id":"/tenants/12345a7c-c56d-43e8-9549-dd230ce8a038","tenantId":"12345a7c-c56d-43e8-9549-dd230ce8a038"}]}` - m.On("queryAPIWithHeader", ctx, ce.GetTenantQueryURL(), "Bearer firstAccessToken").Return([]byte(authBody), 200, nil) - - azureLogin, err := testLoginService(t, m, nil) - assert.NilError(t, err) - - err = azureLogin.Login(ctx, "00000000-c56d-43e8-9549-dd230ce8a038", AzurePublicCloudName) - assert.Error(t, err, "could not find requested azure tenant 00000000-c56d-43e8-9549-dd230ce8a038: login failed") -} - -func TestLoginRequestedTenantNotFound(t *testing.T) { - var redirectURL string - m := &MockAzureHelper{} - ce, err := CloudEnvironments.Get(AzurePublicCloudName) - assert.NilError(t, err) - - m.On("openAzureLoginPage", mock.AnythingOfType("string"), mock.AnythingOfType("CloudEnvironment")).Run(func(args mock.Arguments) { - redirectURL = args.Get(0).(string) - err := queryKeyValue(redirectURL, "code", "123456879") - assert.NilError(t, err) - }).Return(nil) - - m.On("queryToken", mock.AnythingOfType("login.CloudEnvironment"), mock.MatchedBy(func(data url.Values) bool { - //Need a matcher here because the value of redirectUrl is not known until executing openAzureLoginPage - return reflect.DeepEqual(data, url.Values{ - "grant_type": []string{"authorization_code"}, - "client_id": []string{clientID}, - "code": []string{"123456879"}, - "scope": []string{ce.GetTokenScope()}, - "redirect_uri": []string{redirectURL}, - }) - }), "organizations").Return(azureToken{ - RefreshToken: "firstRefreshToken", - AccessToken: "firstAccessToken", - ExpiresIn: 3600, - Foci: "1", - }, nil) - - ctx := context.TODO() - authBody := `{"value":[]}` - m.On("queryAPIWithHeader", ctx, ce.GetTenantQueryURL(), "Bearer firstAccessToken").Return([]byte(authBody), 200, nil) - - azureLogin, err := testLoginService(t, m, nil) - assert.NilError(t, err) - - err = azureLogin.Login(ctx, "", AzurePublicCloudName) - assert.Error(t, err, "could not find azure tenant: login failed") -} - -func TestLoginAuthorizationFailed(t *testing.T) { - var redirectURL string - m := &MockAzureHelper{} - ce, err := CloudEnvironments.Get(AzurePublicCloudName) - assert.NilError(t, err) - - m.On("openAzureLoginPage", mock.AnythingOfType("string"), mock.AnythingOfType("CloudEnvironment")).Run(func(args mock.Arguments) { - redirectURL = args.Get(0).(string) - err := queryKeyValue(redirectURL, "code", "123456879") - assert.NilError(t, err) - }).Return(nil) - - m.On("queryToken", mock.AnythingOfType("login.CloudEnvironment"), mock.MatchedBy(func(data url.Values) bool { - //Need a matcher here because the value of redirectUrl is not known until executing openAzureLoginPage - return reflect.DeepEqual(data, url.Values{ - "grant_type": []string{"authorization_code"}, - "client_id": []string{clientID}, - "code": []string{"123456879"}, - "scope": []string{ce.GetTokenScope()}, - "redirect_uri": []string{redirectURL}, - }) - }), "organizations").Return(azureToken{ - RefreshToken: "firstRefreshToken", - AccessToken: "firstAccessToken", - ExpiresIn: 3600, - Foci: "1", - }, nil) - - authBody := `[access denied]` - - ctx := context.TODO() - m.On("queryAPIWithHeader", ctx, ce.GetTenantQueryURL(), "Bearer firstAccessToken").Return([]byte(authBody), 400, nil) - - azureLogin, err := testLoginService(t, m, nil) - assert.NilError(t, err) - - err = azureLogin.Login(ctx, "", AzurePublicCloudName) - assert.Error(t, err, "unable to login status code 400: [access denied]: login failed") -} - -func TestValidThroughDeviceCodeFlow(t *testing.T) { - m := &MockAzureHelper{} - ce, err := CloudEnvironments.Get(AzurePublicCloudName) - assert.NilError(t, err) - - m.On("openAzureLoginPage", mock.AnythingOfType("string"), mock.AnythingOfType("CloudEnvironment")).Return(errors.New("Could not open browser")) - m.On("getDeviceCodeFlowToken", mock.AnythingOfType("CloudEnvironment")).Return(adal.Token{AccessToken: "firstAccessToken", RefreshToken: "firstRefreshToken"}, nil) - - authBody := `{"value":[{"id":"/tenants/12345a7c-c56d-43e8-9549-dd230ce8a038","tenantId":"12345a7c-c56d-43e8-9549-dd230ce8a038"}]}` - - ctx := context.TODO() - m.On("queryAPIWithHeader", ctx, ce.GetTenantQueryURL(), "Bearer firstAccessToken").Return([]byte(authBody), 200, nil) - data := refreshTokenData("firstRefreshToken", ce) - m.On("queryToken", mock.AnythingOfType("login.CloudEnvironment"), data, "12345a7c-c56d-43e8-9549-dd230ce8a038").Return(azureToken{ - RefreshToken: "newRefreshToken", - AccessToken: "newAccessToken", - ExpiresIn: 3600, - Foci: "1", - }, nil) - azureLogin, err := testLoginService(t, m, nil) - assert.NilError(t, err) - - err = azureLogin.Login(ctx, "", AzurePublicCloudName) - assert.NilError(t, err) - - loginToken, err := azureLogin.tokenStore.readToken() - assert.NilError(t, err) - assert.Equal(t, loginToken.Token.AccessToken, "newAccessToken") - assert.Equal(t, loginToken.Token.RefreshToken, "newRefreshToken") - assert.Assert(t, time.Now().Add(3500*time.Second).Before(loginToken.Token.Expiry)) - assert.Equal(t, loginToken.TenantID, "12345a7c-c56d-43e8-9549-dd230ce8a038") - assert.Equal(t, loginToken.Token.Type(), "Bearer") - assert.Equal(t, loginToken.CloudEnvironment, "AzureCloud") -} - -func TestNonstandardCloudEnvironment(t *testing.T) { - dockerCloudMetadata := []byte(` - [{ - "authentication": { - "loginEndpoint": "https://login.docker.com/", - "audiences": [ - "https://management.docker.com/", - "https://management.cli.docker.com/" - ], - "tenant": "F5773994-FE88-482E-9E33-6E799D250416" - }, - "name": "AzureDockerCloud", - "suffixes": { - "acrLoginServer": "azurecr.docker.io" - }, - "resourceManager": "https://management.docker.com/" - }]`) - var metadataReqCount int32 - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, err := w.Write(dockerCloudMetadata) - assert.NilError(t, err) - atomic.AddInt32(&metadataReqCount, 1) - })) - defer srv.Close() - - cloudMetadataURL, cloudMetadataURLSet := os.LookupEnv(CloudMetadataURLVar) - if cloudMetadataURLSet { - defer func() { - err := os.Setenv(CloudMetadataURLVar, cloudMetadataURL) - assert.NilError(t, err) - }() - } - err := os.Setenv(CloudMetadataURLVar, srv.URL) - assert.NilError(t, err) - - ctx := context.TODO() - - ces := newCloudEnvironmentService() - ces.cloudMetadataURL = srv.URL - dockerCloudEnv, err := ces.Get("AzureDockerCloud") - assert.NilError(t, err) - - helperMock := &MockAzureHelper{} - var redirectURL string - helperMock.On("openAzureLoginPage", mock.AnythingOfType("string"), mock.AnythingOfType("CloudEnvironment")).Run(func(args mock.Arguments) { - redirectURL = args.Get(0).(string) - err := queryKeyValue(redirectURL, "code", "123456879") - assert.NilError(t, err) - }).Return(nil) - - helperMock.On("queryToken", mock.AnythingOfType("login.CloudEnvironment"), mock.MatchedBy(func(data url.Values) bool { - //Need a matcher here because the value of redirectUrl is not known until executing openAzureLoginPage - return reflect.DeepEqual(data, url.Values{ - "grant_type": []string{"authorization_code"}, - "client_id": []string{clientID}, - "code": []string{"123456879"}, - "scope": []string{dockerCloudEnv.GetTokenScope()}, - "redirect_uri": []string{redirectURL}, - }) - }), "organizations").Return(azureToken{ - RefreshToken: "firstRefreshToken", - AccessToken: "firstAccessToken", - ExpiresIn: 3600, - Foci: "1", - }, nil) - - authBody := `{"value":[{"id":"/tenants/F5773994-FE88-482E-9E33-6E799D250416","tenantId":"F5773994-FE88-482E-9E33-6E799D250416"}]}` - - helperMock.On("queryAPIWithHeader", ctx, dockerCloudEnv.GetTenantQueryURL(), "Bearer firstAccessToken").Return([]byte(authBody), 200, nil) - data := refreshTokenData("firstRefreshToken", dockerCloudEnv) - helperMock.On("queryToken", mock.AnythingOfType("login.CloudEnvironment"), data, "F5773994-FE88-482E-9E33-6E799D250416").Return(azureToken{ - RefreshToken: "newRefreshToken", - AccessToken: "newAccessToken", - ExpiresIn: 3600, - Foci: "1", - }, nil) - - azureLogin, err := testLoginService(t, helperMock, ces) - assert.NilError(t, err) - - err = azureLogin.Login(ctx, "", "AzureDockerCloud") - assert.NilError(t, err) - - loginToken, err := azureLogin.tokenStore.readToken() - assert.NilError(t, err) - assert.Equal(t, loginToken.Token.AccessToken, "newAccessToken") - assert.Equal(t, loginToken.Token.RefreshToken, "newRefreshToken") - assert.Assert(t, time.Now().Add(3500*time.Second).Before(loginToken.Token.Expiry)) - assert.Equal(t, loginToken.TenantID, "F5773994-FE88-482E-9E33-6E799D250416") - assert.Equal(t, loginToken.Token.Type(), "Bearer") - assert.Equal(t, loginToken.CloudEnvironment, "AzureDockerCloud") - assert.Equal(t, metadataReqCount, int32(1)) -} - -// Don't warn about refreshToken parameter taking the same value for all invocations -// nolint:unparam -func refreshTokenData(refreshToken string, ce CloudEnvironment) url.Values { - return url.Values{ - "grant_type": []string{"refresh_token"}, - "client_id": []string{clientID}, - "scope": []string{ce.GetTokenScope()}, - "refresh_token": []string{refreshToken}, - } -} - -func queryKeyValue(redirectURL string, key string, value string) error { - req, err := http.NewRequest("GET", redirectURL, nil) - if err != nil { - return err - } - q := req.URL.Query() - q.Add(key, value) - req.URL.RawQuery = q.Encode() - client := &http.Client{} - _, err = client.Do(req) - return err -} - -type MockAzureHelper struct { - mock.Mock -} - -func (s *MockAzureHelper) getDeviceCodeFlowToken(ce CloudEnvironment) (adal.Token, error) { - args := s.Called(ce) - return args.Get(0).(adal.Token), args.Error(1) -} - -func (s *MockAzureHelper) queryToken(ce CloudEnvironment, data url.Values, tenantID string) (token azureToken, err error) { - args := s.Called(ce, data, tenantID) - return args.Get(0).(azureToken), args.Error(1) -} - -func (s *MockAzureHelper) queryAPIWithHeader(ctx context.Context, authorizationURL string, authorizationHeader string) ([]byte, int, error) { - args := s.Called(ctx, authorizationURL, authorizationHeader) - return args.Get(0).([]byte), args.Int(1), args.Error(2) -} - -func (s *MockAzureHelper) openAzureLoginPage(redirectURL string, ce CloudEnvironment) error { - args := s.Called(redirectURL, ce) - return args.Error(0) -} - -type MockCloudEnvironmentService struct { - mock.Mock -} - -func (s *MockCloudEnvironmentService) Get(name string) (CloudEnvironment, error) { - args := s.Called(name) - return args.Get(0).(CloudEnvironment), args.Error(1) -} diff --git a/aci/login/storagelogin.go b/aci/login/storagelogin.go deleted file mode 100644 index fbaefb064..000000000 --- a/aci/login/storagelogin.go +++ /dev/null @@ -1,55 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package login - -import ( - "context" - "fmt" - - "github.com/pkg/errors" - - "github.com/docker/compose-cli/api/context/store" -) - -// StorageLogin helper for Azure Storage Login -type StorageLogin interface { - // GetAzureStorageAccountKey retrieves the storage account ket from the current azure login - GetAzureStorageAccountKey(ctx context.Context, accountName string) (string, error) -} - -// StorageLoginImpl implementation of StorageLogin -type StorageLoginImpl struct { - AciContext store.AciContext -} - -// GetAzureStorageAccountKey retrieves the storage account ket from the current azure login -func (helper StorageLoginImpl) GetAzureStorageAccountKey(ctx context.Context, accountName string) (string, error) { - client, err := NewStorageAccountsClient(helper.AciContext.SubscriptionID) - if err != nil { - return "", err - } - result, err := client.ListKeys(ctx, helper.AciContext.ResourceGroup, accountName, "") - if err != nil { - return "", errors.Wrap(err, fmt.Sprintf("could not access storage account acountKeys for %s, using the azure login", accountName)) - } - if result.Keys != nil && len((*result.Keys)) < 1 { - return "", fmt.Errorf("no key could be obtained for storage account %s from your azure login", accountName) - } - - key := (*result.Keys)[0] - return *key.Value, nil -} diff --git a/aci/login/token_store.go b/aci/login/token_store.go deleted file mode 100644 index 131006888..000000000 --- a/aci/login/token_store.go +++ /dev/null @@ -1,94 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package login - -import ( - "encoding/json" - "errors" - "io/ioutil" - "os" - "path/filepath" - - "github.com/Azure/go-autorest/autorest/azure/cli" - - "golang.org/x/oauth2" -) - -type tokenStore struct { - filePath string -} - -// TokenInfo data stored in tokenStore -type TokenInfo struct { - Token oauth2.Token `json:"oauthToken"` - TenantID string `json:"tenantId"` - CloudEnvironment string `json:"cloudEnvironment"` -} - -func newTokenStore(path string) (tokenStore, error) { - parentFolder := filepath.Dir(path) - dir, err := os.Stat(parentFolder) - if os.IsNotExist(err) { - err = os.MkdirAll(parentFolder, 0700) - if err != nil { - return tokenStore{}, err - } - dir, err = os.Stat(parentFolder) - } - if err != nil { - return tokenStore{}, err - } - if !dir.Mode().IsDir() { - return tokenStore{}, errors.New("cannot use path " + path + " ; " + parentFolder + " already exists and is not a directory") - } - return tokenStore{ - filePath: path, - }, nil -} - -// GetTokenStorePath the path for token store -func GetTokenStorePath() string { - cliPath, _ := cli.AccessTokensPath() - return filepath.Join(filepath.Dir(cliPath), tokenStoreFilename) -} - -func (store tokenStore) writeLoginInfo(info TokenInfo) error { - bytes, err := json.MarshalIndent(info, "", " ") - if err != nil { - return err - } - return ioutil.WriteFile(store.filePath, bytes, 0644) -} - -func (store tokenStore) readToken() (TokenInfo, error) { - bytes, err := ioutil.ReadFile(store.filePath) - if err != nil { - return TokenInfo{}, err - } - loginInfo := TokenInfo{} - if err := json.Unmarshal(bytes, &loginInfo); err != nil { - return TokenInfo{}, err - } - if loginInfo.CloudEnvironment == "" { - loginInfo.CloudEnvironment = AzurePublicCloudName - } - return loginInfo, nil -} - -func (store tokenStore) removeData() error { - return os.Remove(store.filePath) -} diff --git a/aci/login/token_store_test.go b/aci/login/token_store_test.go deleted file mode 100644 index 4eef8d769..000000000 --- a/aci/login/token_store_test.go +++ /dev/null @@ -1,59 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package login - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" - - "gotest.tools/v3/assert" -) - -func TestCreateStoreFromExistingFolder(t *testing.T) { - existingDir, err := ioutil.TempDir("", "test_store") - assert.NilError(t, err) - - storePath := filepath.Join(existingDir, tokenStoreFilename) - store, err := newTokenStore(storePath) - assert.NilError(t, err) - assert.Equal(t, store.filePath, storePath) -} - -func TestCreateStoreFromNonExistingFolder(t *testing.T) { - existingDir, err := ioutil.TempDir("", "test_store") - assert.NilError(t, err) - - storePath := filepath.Join(existingDir, "new", tokenStoreFilename) - store, err := newTokenStore(storePath) - assert.NilError(t, err) - assert.Equal(t, store.filePath, storePath) - - newDir, err := os.Stat(filepath.Join(existingDir, "new")) - assert.NilError(t, err) - assert.Assert(t, newDir.Mode().IsDir()) -} - -func TestErrorIfParentFolderIsAFile(t *testing.T) { - existingDir, err := ioutil.TempFile("", "test_store") - assert.NilError(t, err) - - storePath := filepath.Join(existingDir.Name(), tokenStoreFilename) - _, err = newTokenStore(storePath) - assert.Error(t, err, "cannot use path "+storePath+" ; "+existingDir.Name()+" already exists and is not a directory") -} diff --git a/aci/resource_group.go b/aci/resource_group.go deleted file mode 100644 index 07513c6c5..000000000 --- a/aci/resource_group.go +++ /dev/null @@ -1,124 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package aci - -import ( - "context" - - "github.com/Azure/azure-sdk-for-go/profiles/2019-03-01/resources/mgmt/resources" - "github.com/Azure/azure-sdk-for-go/profiles/preview/preview/subscription/mgmt/subscription" - "github.com/pkg/errors" - - "github.com/docker/compose-cli/aci/login" -) - -// ResourceGroupHelper interface to manage resource groups and subscription IDs -type ResourceGroupHelper interface { - GetSubscriptionIDs(ctx context.Context) ([]subscription.Model, error) - ListGroups(ctx context.Context, subscriptionID string) ([]resources.Group, error) - GetGroup(ctx context.Context, subscriptionID string, groupName string) (resources.Group, error) - CreateOrUpdate(ctx context.Context, subscriptionID string, resourceGroupName string, parameters resources.Group) (result resources.Group, err error) - DeleteAsync(ctx context.Context, subscriptionID string, resourceGroupName string) error -} - -type aciResourceGroupHelperImpl struct { -} - -// NewACIResourceGroupHelper create a new ResourceGroupHelper -func NewACIResourceGroupHelper() ResourceGroupHelper { - return aciResourceGroupHelperImpl{} -} - -// GetGroup get a resource group from its name -func (mgt aciResourceGroupHelperImpl) GetGroup(ctx context.Context, subscriptionID string, groupName string) (resources.Group, error) { - gc, err := login.NewGroupsClient(subscriptionID) - if err != nil { - return resources.Group{}, err - } - return gc.Get(ctx, groupName) -} - -// ListGroups list resource groups -func (mgt aciResourceGroupHelperImpl) ListGroups(ctx context.Context, subscriptionID string) ([]resources.Group, error) { - gc, err := login.NewGroupsClient(subscriptionID) - if err != nil { - return nil, err - } - - groupResponse, err := gc.List(ctx, "", nil) - if err != nil { - return nil, err - } - - groups := groupResponse.Values() - - for groupResponse.NotDone() { - err = groupResponse.NextWithContext(ctx) - if err != nil { - return nil, err - } - newValues := groupResponse.Values() - groups = append(groups, newValues...) - } - - return groups, nil -} - -// CreateOrUpdate create or update a resource group -func (mgt aciResourceGroupHelperImpl) CreateOrUpdate(ctx context.Context, subscriptionID string, resourceGroupName string, parameters resources.Group) (result resources.Group, err error) { - gc, err := login.NewGroupsClient(subscriptionID) - if err != nil { - return resources.Group{}, err - } - return gc.CreateOrUpdate(ctx, resourceGroupName, parameters) -} - -// DeleteAsync deletes a resource group. Does not wait for full deletion to return (long operation) -func (mgt aciResourceGroupHelperImpl) DeleteAsync(ctx context.Context, subscriptionID string, resourceGroupName string) (err error) { - gc, err := login.NewGroupsClient(subscriptionID) - if err != nil { - return err - } - - _, err = gc.Delete(ctx, resourceGroupName) - return err -} - -// GetSubscriptionIDs Return available subscription IDs based on azure login -func (mgt aciResourceGroupHelperImpl) GetSubscriptionIDs(ctx context.Context) ([]subscription.Model, error) { - c, err := login.NewSubscriptionsClient() - if err != nil { - return nil, err - } - res, err := c.List(ctx) - if err != nil { - return nil, err - } - subs := res.Values() - - if len(subs) == 0 { - return nil, errors.New("no subscriptions found") - } - for res.NotDone() { - err = res.NextWithContext(ctx) - if err != nil { - return nil, err - } - subs = append(subs, res.Values()...) - } - return subs, nil -} diff --git a/aci/resources.go b/aci/resources.go deleted file mode 100644 index c483de1b1..000000000 --- a/aci/resources.go +++ /dev/null @@ -1,66 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package aci - -import ( - "context" - "fmt" - - "github.com/hashicorp/go-multierror" - - "github.com/docker/compose-cli/aci/convert" - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/api/resources" -) - -type aciResourceService struct { - aciContext store.AciContext -} - -func (cs *aciResourceService) Prune(ctx context.Context, request resources.PruneRequest) (resources.PruneResult, error) { - res, err := getACIContainerGroups(ctx, cs.aciContext.SubscriptionID, cs.aciContext.ResourceGroup) - result := resources.PruneResult{} - if err != nil { - return result, err - } - multierr := &multierror.Error{} - deleted := []string{} - cpus := 0. - mem := 0. - - for _, containerGroup := range res { - if !request.Force && convert.GetGroupStatus(containerGroup) == "Node "+convert.StatusRunning { - continue - } - - for _, container := range *containerGroup.Containers { - hostConfig := convert.ToHostConfig(container, containerGroup) - cpus += hostConfig.CPUReservation - mem += convert.BytesToGB(float64(hostConfig.MemoryReservation)) - } - - if !request.DryRun { - _, err := deleteACIContainerGroup(ctx, cs.aciContext, *containerGroup.Name) - multierr = multierror.Append(multierr, err) - } - - deleted = append(deleted, *containerGroup.Name) - } - result.DeletedIDs = deleted - result.Summary = fmt.Sprintf("Total CPUs reclaimed: %.2f, total memory reclaimed: %.2f GB", cpus, mem) - return result, multierr.ErrorOrNil() -} diff --git a/aci/volumes.go b/aci/volumes.go deleted file mode 100644 index 530c836ad..000000000 --- a/aci/volumes.go +++ /dev/null @@ -1,276 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package aci - -import ( - "context" - "fmt" - "net/http" - "strings" - - "github.com/pkg/errors" - - "github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2019-12-01/containerinstance" - "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage" - "github.com/Azure/go-autorest/autorest/to" - - "github.com/docker/compose-cli/aci/login" - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/api/volumes" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/progress" -) - -type aciVolumeService struct { - aciContext store.AciContext -} - -func (cs *aciVolumeService) List(ctx context.Context) ([]volumes.Volume, error) { - accountClient, err := login.NewStorageAccountsClient(cs.aciContext.SubscriptionID) - if err != nil { - return nil, err - } - result, err := accountClient.ListByResourceGroup(ctx, cs.aciContext.ResourceGroup) - if err != nil { - return nil, err - } - accounts := result.Value - fileShareClient, err := login.NewFileShareClient(cs.aciContext.SubscriptionID) - if err != nil { - return nil, err - } - fileShares := []volumes.Volume{} - for _, account := range *accounts { - fileSharePage, err := fileShareClient.List(ctx, cs.aciContext.ResourceGroup, *account.Name, "", "", "") - if err != nil { - return nil, err - } - - for fileSharePage.NotDone() { - values := fileSharePage.Values() - for _, fileShare := range values { - fileShares = append(fileShares, toVolume(*account.Name, *fileShare.Name)) - } - if err := fileSharePage.NextWithContext(ctx); err != nil { - return nil, err - } - } - } - return fileShares, nil -} - -// VolumeCreateOptions options to create a new ACI volume -type VolumeCreateOptions struct { - Account string -} - -func (cs *aciVolumeService) Create(ctx context.Context, name string, options interface{}) (volumes.Volume, error) { - opts, ok := options.(*VolumeCreateOptions) - if !ok || opts == nil { - return volumes.Volume{}, errors.New("could not read Azure VolumeCreateOptions struct from generic parameter") - } - w := progress.ContextWriter(ctx) - w.Event(progress.NewEvent(opts.Account, progress.Working, "Validating")) - accountClient, err := login.NewStorageAccountsClient(cs.aciContext.SubscriptionID) - if err != nil { - w.Event(progress.ErrorEvent(opts.Account)) - return volumes.Volume{}, err - } - account, err := accountClient.GetProperties(ctx, cs.aciContext.ResourceGroup, opts.Account, "") - if err == nil { - w.Event(progress.NewEvent(opts.Account, progress.Done, "Use existing")) - } else if !account.HasHTTPStatus(http.StatusNotFound) { - w.Event(progress.ErrorEvent(opts.Account)) - return volumes.Volume{}, err - } else { - result, err := accountClient.CheckNameAvailability(ctx, storage.AccountCheckNameAvailabilityParameters{ - Name: to.StringPtr(opts.Account), - Type: to.StringPtr("Microsoft.Storage/storageAccounts"), - }) - if err != nil { - w.Event(progress.ErrorEvent(opts.Account)) - return volumes.Volume{}, err - } - if !*result.NameAvailable { - w.Event(progress.ErrorEvent(opts.Account)) - return volumes.Volume{}, errors.New("error: " + *result.Message) - } - parameters := defaultStorageAccountParams(cs.aciContext) - - w.Event(progress.CreatingEvent(opts.Account)) - - future, err := accountClient.Create(ctx, cs.aciContext.ResourceGroup, opts.Account, parameters) - if err != nil { - w.Event(progress.ErrorEvent(opts.Account)) - return volumes.Volume{}, err - } - if err := future.WaitForCompletionRef(ctx, accountClient.Client); err != nil { - w.Event(progress.ErrorEvent(opts.Account)) - return volumes.Volume{}, err - } - account, err = future.Result(accountClient) - if err != nil { - w.Event(progress.ErrorEvent(opts.Account)) - return volumes.Volume{}, err - } - w.Event(progress.CreatedEvent(opts.Account)) - } - w.Event(progress.CreatingEvent(name)) - fileShareClient, err := login.NewFileShareClient(cs.aciContext.SubscriptionID) - if err != nil { - return volumes.Volume{}, err - } - - fileShare, err := fileShareClient.Get(ctx, cs.aciContext.ResourceGroup, *account.Name, name, "") - if err == nil { - w.Event(progress.ErrorEvent(name)) - return volumes.Volume{}, errors.Wrapf(api.ErrAlreadyExists, "Azure fileshare %q already exists", name) - } - if !fileShare.HasHTTPStatus(http.StatusNotFound) { - w.Event(progress.ErrorEvent(name)) - return volumes.Volume{}, err - } - fileShare, err = fileShareClient.Create(ctx, cs.aciContext.ResourceGroup, *account.Name, name, storage.FileShare{}) - if err != nil { - w.Event(progress.ErrorEvent(name)) - return volumes.Volume{}, err - } - w.Event(progress.CreatedEvent(name)) - return toVolume(*account.Name, *fileShare.Name), nil -} - -func checkVolumeUsage(ctx context.Context, aciContext store.AciContext, id string) error { - containerGroups, err := getACIContainerGroups(ctx, aciContext.SubscriptionID, aciContext.ResourceGroup) - if err != nil { - return err - } - for _, cg := range containerGroups { - if hasVolume(cg.Volumes, id) { - return errors.Errorf("volume %q is used in container group %q", - id, *cg.Name) - } - } - return nil -} - -func hasVolume(volumes *[]containerinstance.Volume, id string) bool { - if volumes == nil { - return false - } - for _, v := range *volumes { - if v.AzureFile != nil && v.AzureFile.StorageAccountName != nil && v.AzureFile.ShareName != nil && - (*v.AzureFile.StorageAccountName+"/"+*v.AzureFile.ShareName) == id { - return true - } - } - return false -} - -func (cs *aciVolumeService) Delete(ctx context.Context, id string, options interface{}) error { - err := checkVolumeUsage(ctx, cs.aciContext, id) - if err != nil { - return err - } - storageAccount, fileshare, err := getStorageAccountAndFileshare(id) - if err != nil { - return err - } - - fileShareClient, err := login.NewFileShareClient(cs.aciContext.SubscriptionID) - if err != nil { - return err - } - fileShareItemsPage, err := fileShareClient.List(ctx, cs.aciContext.ResourceGroup, storageAccount, "", "", "") - if err != nil { - return err - } - fileshares := fileShareItemsPage.Values() - if len(fileshares) == 1 && *fileshares[0].Name == fileshare { - storageAccountsClient, err := login.NewStorageAccountsClient(cs.aciContext.SubscriptionID) - if err != nil { - return err - } - account, err := storageAccountsClient.GetProperties(ctx, cs.aciContext.ResourceGroup, storageAccount, "") - if err != nil { - return err - } - if err == nil { - if _, ok := account.Tags[dockerVolumeTag]; ok { - result, err := storageAccountsClient.Delete(ctx, cs.aciContext.ResourceGroup, storageAccount) - if result.IsHTTPStatus(http.StatusNoContent) { - return errors.Wrapf(api.ErrNotFound, "storage account %s does not exist", storageAccount) - } - return err - } - } - } - - result, err := fileShareClient.Delete(ctx, cs.aciContext.ResourceGroup, storageAccount, fileshare) - if result.HasHTTPStatus(http.StatusNoContent) { - return errors.Wrapf(api.ErrNotFound, "fileshare %q", fileshare) - } - return err -} - -func (cs *aciVolumeService) Inspect(ctx context.Context, id string) (volumes.Volume, error) { - storageAccount, fileshareName, err := getStorageAccountAndFileshare(id) - if err != nil { - return volumes.Volume{}, err - } - fileShareClient, err := login.NewFileShareClient(cs.aciContext.SubscriptionID) - if err != nil { - return volumes.Volume{}, err - } - res, err := fileShareClient.Get(ctx, cs.aciContext.ResourceGroup, storageAccount, fileshareName, "") - if err != nil { // Just checks if it exists - if res.HasHTTPStatus(http.StatusNotFound) { - return volumes.Volume{}, errors.Wrapf(api.ErrNotFound, "account %q, file share %q. Original message %s", storageAccount, fileshareName, err.Error()) - } - return volumes.Volume{}, err - } - return toVolume(storageAccount, fileshareName), nil -} - -func toVolume(storageAccountName string, fileShareName string) volumes.Volume { - return volumes.Volume{ - ID: volumeID(storageAccountName, fileShareName), - Description: fmt.Sprintf("Fileshare %s in %s storage account", fileShareName, storageAccountName), - } -} - -func volumeID(storageAccount string, fileShareName string) string { - return fmt.Sprintf("%s/%s", storageAccount, fileShareName) -} - -func defaultStorageAccountParams(aciContext store.AciContext) storage.AccountCreateParameters { - tags := map[string]*string{dockerVolumeTag: to.StringPtr(dockerVolumeTag)} - return storage.AccountCreateParameters{ - Location: to.StringPtr(aciContext.Location), - Sku: &storage.Sku{ - Name: storage.StandardLRS, - }, - Tags: tags, - } -} - -func getStorageAccountAndFileshare(volumeID string) (string, string, error) { - tokens := strings.Split(volumeID, "/") - if len(tokens) != 2 { - return "", "", errors.New("invalid format for volume ID, expected storageaccount/fileshare") - } - return tokens[0], tokens[1], nil -} diff --git a/api/backend/backend.go b/api/backend/backend.go deleted file mode 100644 index c278a3c06..000000000 --- a/api/backend/backend.go +++ /dev/null @@ -1,118 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package backend - -import ( - "errors" - "fmt" - - "github.com/sirupsen/logrus" - - "github.com/docker/compose-cli/api/cloud" - "github.com/docker/compose-cli/api/containers" - "github.com/docker/compose-cli/api/resources" - "github.com/docker/compose-cli/api/secrets" - "github.com/docker/compose-cli/api/volumes" - "github.com/docker/compose-cli/pkg/api" -) - -var ( - errNoType = errors.New("backend: no type") - errNoName = errors.New("backend: no name") - errTypeRegistered = errors.New("backend: already registered") -) - -type initFunc func() (Service, error) -type getCloudServiceFunc func() (cloud.Service, error) - -type registeredBackend struct { - name string - backendType string - init initFunc - getCloudService getCloudServiceFunc -} - -var backends = struct { - r []*registeredBackend -}{} - -var instance Service - -// Current return the active backend instance -func Current() Service { - return instance -} - -// WithBackend set the active backend instance -func WithBackend(s Service) { - instance = s -} - -// Service aggregates the service interfaces -type Service interface { - ContainerService() containers.Service - ComposeService() api.Service - ResourceService() resources.Service - SecretsService() secrets.Service - VolumeService() volumes.Service -} - -// Register adds a typed backend to the registry -func Register(name string, backendType string, init initFunc, getCoudService getCloudServiceFunc) { - if name == "" { - logrus.Fatal(errNoName) - } - if backendType == "" { - logrus.Fatal(errNoType) - } - for _, b := range backends.r { - if b.backendType == backendType { - logrus.Fatal(errTypeRegistered) - } - } - - backends.r = append(backends.r, ®isteredBackend{ - name, - backendType, - init, - getCoudService, - }) -} - -// Get returns the backend registered for a particular type, it returns -// an error if there is no registered backends for the given type. -func Get(backendType string) (Service, error) { - for _, b := range backends.r { - if b.backendType == backendType { - return b.init() - } - } - - return nil, api.ErrNotFound -} - -// GetCloudService returns the backend registered for a particular type, it returns -// an error if there is no registered backends for the given type. -func GetCloudService(backendType string) (cloud.Service, error) { - for _, b := range backends.r { - if b.backendType == backendType { - return b.getCloudService() - } - } - - return nil, fmt.Errorf("backend not found for backend type %s", backendType) -} diff --git a/api/client/client.go b/api/client/client.go deleted file mode 100644 index 67144f34f..000000000 --- a/api/client/client.go +++ /dev/null @@ -1,119 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package client - -import ( - "context" - - "github.com/docker/compose-cli/api/backend" - "github.com/docker/compose-cli/api/cloud" - "github.com/docker/compose-cli/api/containers" - apicontext "github.com/docker/compose-cli/api/context" - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/api/resources" - "github.com/docker/compose-cli/api/secrets" - "github.com/docker/compose-cli/api/volumes" - "github.com/docker/compose-cli/pkg/api" -) - -// New returns a backend client associated with current context -func New(ctx context.Context) (*Client, error) { - currentContext := apicontext.Current() - s := store.Instance() - - cc, err := s.Get(currentContext) - if err != nil { - return nil, err - } - - service := backend.Current() - if service == nil { - return nil, api.ErrNotFound - } - - client := NewClient(cc.Type(), service) - return &client, nil -} - -// NewClient returns new client -func NewClient(backendType string, service backend.Service) Client { - return Client{ - backendType: backendType, - bs: service, - } -} - -// GetCloudService returns a backend CloudService (typically login, create context) -func GetCloudService(ctx context.Context, backendType string) (cloud.Service, error) { - return backend.GetCloudService(backendType) -} - -// Client is a multi-backend client -type Client struct { - backendType string - bs backend.Service -} - -// ContextType the context type associated with backend -func (c *Client) ContextType() string { - return c.backendType -} - -// ContainerService returns the backend service for the current context -func (c *Client) ContainerService() containers.Service { - if cs := c.bs.ContainerService(); cs != nil { - return cs - } - - return &containerService{} -} - -// ComposeService returns the backend service for the current context -func (c *Client) ComposeService() api.Service { - if cs := c.bs.ComposeService(); cs != nil { - return cs - } - - return &composeService{} -} - -// SecretsService returns the backend service for the current context -func (c *Client) SecretsService() secrets.Service { - if ss := c.bs.SecretsService(); ss != nil { - return ss - } - - return &secretsService{} -} - -// VolumeService returns the backend service for the current context -func (c *Client) VolumeService() volumes.Service { - if vs := c.bs.VolumeService(); vs != nil { - return vs - } - - return &volumeService{} -} - -// ResourceService returns the backend service for the current context -func (c *Client) ResourceService() resources.Service { - if vs := c.bs.ResourceService(); vs != nil { - return vs - } - - return &resourceService{} -} diff --git a/api/client/compose.go b/api/client/compose.go deleted file mode 100644 index 1b6f58216..000000000 --- a/api/client/compose.go +++ /dev/null @@ -1,123 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package client - -import ( - "context" - - "github.com/compose-spec/compose-go/types" - "github.com/docker/compose-cli/pkg/api" -) - -type composeService struct { -} - -func (c *composeService) Build(ctx context.Context, project *types.Project, options api.BuildOptions) error { - return api.ErrNotImplemented -} - -func (c *composeService) Push(ctx context.Context, project *types.Project, options api.PushOptions) error { - return api.ErrNotImplemented -} - -func (c *composeService) Pull(ctx context.Context, project *types.Project, options api.PullOptions) error { - return api.ErrNotImplemented -} - -func (c *composeService) Create(ctx context.Context, project *types.Project, opts api.CreateOptions) error { - return api.ErrNotImplemented -} - -func (c *composeService) Start(ctx context.Context, project *types.Project, options api.StartOptions) error { - return api.ErrNotImplemented -} - -func (c *composeService) Restart(ctx context.Context, project *types.Project, options api.RestartOptions) error { - return api.ErrNotImplemented -} - -func (c *composeService) Stop(ctx context.Context, project *types.Project, options api.StopOptions) error { - return api.ErrNotImplemented -} - -func (c *composeService) Up(context.Context, *types.Project, api.UpOptions) error { - return api.ErrNotImplemented -} - -func (c *composeService) Down(context.Context, string, api.DownOptions) error { - return api.ErrNotImplemented -} - -func (c *composeService) Logs(context.Context, string, api.LogConsumer, api.LogOptions) error { - return api.ErrNotImplemented -} - -func (c *composeService) Ps(context.Context, string, api.PsOptions) ([]api.ContainerSummary, error) { - return nil, api.ErrNotImplemented -} - -func (c *composeService) List(context.Context, api.ListOptions) ([]api.Stack, error) { - return nil, api.ErrNotImplemented -} - -func (c *composeService) Convert(context.Context, *types.Project, api.ConvertOptions) ([]byte, error) { - return nil, api.ErrNotImplemented -} - -func (c *composeService) Kill(ctx context.Context, project *types.Project, options api.KillOptions) error { - return api.ErrNotImplemented -} - -func (c *composeService) RunOneOffContainer(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) { - return 0, api.ErrNotImplemented -} - -func (c *composeService) Remove(ctx context.Context, project *types.Project, options api.RemoveOptions) error { - return api.ErrNotImplemented -} - -func (c *composeService) Exec(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) { - return 0, api.ErrNotImplemented -} - -func (c *composeService) Copy(ctx context.Context, project *types.Project, opts api.CopyOptions) error { - return api.ErrNotImplemented -} - -func (c *composeService) Pause(ctx context.Context, project string, options api.PauseOptions) error { - return api.ErrNotImplemented -} - -func (c *composeService) UnPause(ctx context.Context, project string, options api.PauseOptions) error { - return api.ErrNotImplemented -} - -func (c *composeService) Top(ctx context.Context, projectName string, services []string) ([]api.ContainerProcSummary, error) { - return nil, api.ErrNotImplemented -} - -func (c *composeService) Events(ctx context.Context, project string, options api.EventsOptions) error { - return api.ErrNotImplemented -} - -func (c *composeService) Port(ctx context.Context, project string, service string, port int, options api.PortOptions) (string, int, error) { - return "", 0, api.ErrNotImplemented -} - -func (c *composeService) Images(ctx context.Context, projectName string, options api.ImagesOptions) ([]api.ImageSummary, error) { - return nil, api.ErrNotImplemented -} diff --git a/api/client/containers.go b/api/client/containers.go deleted file mode 100644 index 5269c390d..000000000 --- a/api/client/containers.go +++ /dev/null @@ -1,71 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package client - -import ( - "context" - - "github.com/docker/compose-cli/api/containers" - "github.com/docker/compose-cli/pkg/api" -) - -type containerService struct { -} - -// List returns all the containers -func (c *containerService) List(context.Context, bool) ([]containers.Container, error) { - return nil, api.ErrNotImplemented -} - -// Start starts a stopped container -func (c *containerService) Start(context.Context, string) error { - return api.ErrNotImplemented -} - -// Stop stops the running container -func (c *containerService) Stop(context.Context, string, *uint32) error { - return api.ErrNotImplemented -} - -func (c *containerService) Kill(ctx context.Context, containerID string, signal string) error { - return api.ErrNotImplemented -} - -// Run creates and starts a container -func (c *containerService) Run(context.Context, containers.ContainerConfig) error { - return api.ErrNotImplemented -} - -// Exec executes a command inside a running container -func (c *containerService) Exec(context.Context, string, containers.ExecRequest) error { - return api.ErrNotImplemented -} - -// Logs returns all the logs of a container -func (c *containerService) Logs(context.Context, string, containers.LogsRequest) error { - return api.ErrNotImplemented -} - -// Delete removes containers -func (c *containerService) Delete(context.Context, string, containers.DeleteRequest) error { - return api.ErrNotImplemented -} - -// Inspect get a specific container -func (c *containerService) Inspect(context.Context, string) (containers.Container, error) { - return containers.Container{}, api.ErrNotImplemented -} diff --git a/api/client/resources.go b/api/client/resources.go deleted file mode 100644 index 7f23b41bc..000000000 --- a/api/client/resources.go +++ /dev/null @@ -1,32 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package client - -import ( - "context" - - "github.com/docker/compose-cli/api/resources" - "github.com/docker/compose-cli/pkg/api" -) - -type resourceService struct { -} - -// Prune prune resources -func (c *resourceService) Prune(ctx context.Context, request resources.PruneRequest) (resources.PruneResult, error) { - return resources.PruneResult{}, api.ErrNotImplemented -} diff --git a/api/client/secrets.go b/api/client/secrets.go deleted file mode 100644 index d2545b834..000000000 --- a/api/client/secrets.go +++ /dev/null @@ -1,43 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package client - -import ( - "context" - - "github.com/docker/compose-cli/api/secrets" - "github.com/docker/compose-cli/pkg/api" -) - -type secretsService struct { -} - -func (s *secretsService) CreateSecret(context.Context, secrets.Secret) (string, error) { - return "", api.ErrNotImplemented -} - -func (s *secretsService) InspectSecret(context.Context, string) (secrets.Secret, error) { - return secrets.Secret{}, api.ErrNotImplemented -} - -func (s *secretsService) ListSecrets(context.Context) ([]secrets.Secret, error) { - return nil, api.ErrNotImplemented -} - -func (s *secretsService) DeleteSecret(context.Context, string, bool) error { - return api.ErrNotImplemented -} diff --git a/api/client/volume.go b/api/client/volume.go deleted file mode 100644 index 62cdc9d74..000000000 --- a/api/client/volume.go +++ /dev/null @@ -1,43 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package client - -import ( - "context" - - "github.com/docker/compose-cli/api/volumes" - "github.com/docker/compose-cli/pkg/api" -) - -type volumeService struct { -} - -func (c *volumeService) List(ctx context.Context) ([]volumes.Volume, error) { - return nil, api.ErrNotImplemented -} - -func (c *volumeService) Create(ctx context.Context, name string, options interface{}) (volumes.Volume, error) { - return volumes.Volume{}, api.ErrNotImplemented -} - -func (c *volumeService) Delete(ctx context.Context, id string, options interface{}) error { - return api.ErrNotImplemented -} - -func (c *volumeService) Inspect(ctx context.Context, volumeID string) (volumes.Volume, error) { - return volumes.Volume{}, api.ErrNotImplemented -} diff --git a/api/cloud/api.go b/api/cloud/api.go deleted file mode 100644 index e6ae7ad0f..000000000 --- a/api/cloud/api.go +++ /dev/null @@ -1,54 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package cloud - -import ( - "context" - - "github.com/docker/compose-cli/pkg/api" -) - -// Service cloud specific services -type Service interface { - // Login login to cloud provider - Login(ctx context.Context, params interface{}) error - // Logout logout from cloud provider - Logout(ctx context.Context) error - // CreateContextData create data for cloud context - CreateContextData(ctx context.Context, params interface{}) (contextData interface{}, description string, err error) -} - -// NotImplementedCloudService to use for backend that don't provide cloud services -func NotImplementedCloudService() (Service, error) { - return notImplementedCloudService{}, nil -} - -type notImplementedCloudService struct { -} - -// Logout login to cloud provider -func (cs notImplementedCloudService) Logout(ctx context.Context) error { - return api.ErrNotImplemented -} - -func (cs notImplementedCloudService) Login(ctx context.Context, params interface{}) error { - return api.ErrNotImplemented -} - -func (cs notImplementedCloudService) CreateContextData(ctx context.Context, params interface{}) (interface{}, string, error) { - return nil, "", api.ErrNotImplemented -} diff --git a/api/config/config.go b/api/config/config.go deleted file mode 100644 index 12fe9214e..000000000 --- a/api/config/config.go +++ /dev/null @@ -1,99 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package config - -import ( - "encoding/json" - "io/ioutil" - "os" - "path/filepath" - - "github.com/pkg/errors" - - "github.com/docker/compose-cli/api/context/store" -) - -var configDir string - -// WithDir sets the config directory path in the context -func WithDir(path string) { - configDir = path -} - -// Dir returns the config directory path -func Dir() string { - return configDir -} - -// LoadFile loads the docker configuration -func LoadFile(dir string) (*File, error) { - f := &File{} - err := loadFile(configFilePath(dir), &f) - if err != nil { - return nil, err - } - return f, nil -} - -// WriteCurrentContext writes the selected current context to the Docker -// configuration file. Note, the validity of the context is not checked. -func WriteCurrentContext(dir string, name string) error { - m := map[string]interface{}{} - path := configFilePath(dir) - err := loadFile(path, &m) - if err != nil { - return err - } - // Match existing CLI behavior - if name == store.DefaultContextName { - delete(m, currentContextKey) - } else { - m[currentContextKey] = name - } - return writeFile(path, m) -} - -func writeFile(path string, content map[string]interface{}) error { - d, err := json.MarshalIndent(content, "", "\t") - if err != nil { - return errors.Wrap(err, "unable to marshal config") - } - err = ioutil.WriteFile(path, d, 0644) - return errors.Wrap(err, "unable to write config file") -} - -func loadFile(path string, dest interface{}) error { - data, err := ioutil.ReadFile(path) - if err != nil { - if os.IsNotExist(err) { - // Not an error if there is no config, we're just using defaults - return nil - } - return errors.Wrap(err, "unable to read config file") - } - err = json.Unmarshal(data, dest) - return errors.Wrap(err, "unable to unmarshal config file "+path) -} - -func configFilePath(dir string) string { - return filepath.Join(dir, ConfigFileName) -} - -// File contains the current context from the docker configuration file -type File struct { - CurrentContext string `json:"currentContext,omitempty"` -} diff --git a/api/config/config_test.go b/api/config/config_test.go deleted file mode 100644 index 3986c6f0b..000000000 --- a/api/config/config_test.go +++ /dev/null @@ -1,83 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package config - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" - - "gotest.tools/v3/assert" -) - -var sampleConfig = []byte(`{ - "otherField": "value", - "currentContext": "local" -}`) - -func testConfigDir(t *testing.T) string { - d, _ := ioutil.TempDir("", "") - t.Cleanup(func() { - _ = os.RemoveAll(d) - }) - return d -} - -func writeSampleConfig(t *testing.T, d string) { - err := ioutil.WriteFile(filepath.Join(d, ConfigFileName), sampleConfig, 0644) - assert.NilError(t, err) -} - -func TestLoadFile(t *testing.T) { - d := testConfigDir(t) - writeSampleConfig(t, d) - f, err := LoadFile(d) - assert.NilError(t, err) - assert.Equal(t, f.CurrentContext, "local") -} - -func TestOverWriteCurrentContext(t *testing.T) { - d := testConfigDir(t) - writeSampleConfig(t, d) - f, err := LoadFile(d) - assert.NilError(t, err) - assert.Equal(t, f.CurrentContext, "local") - - err = WriteCurrentContext(d, "overwrite") - assert.NilError(t, err) - f, err = LoadFile(d) - assert.NilError(t, err) - assert.Equal(t, f.CurrentContext, "overwrite") - - m := map[string]interface{}{} - err = loadFile(filepath.Join(d, ConfigFileName), &m) - assert.NilError(t, err) - assert.Equal(t, "overwrite", m["currentContext"]) - assert.Equal(t, "value", m["otherField"]) -} - -// TestWriteDefaultContextToEmptyConfig tests a specific case seen on the CI: -// panic when setting context to default with empty config file -func TestWriteDefaultContextToEmptyConfig(t *testing.T) { - d := testConfigDir(t) - err := WriteCurrentContext(d, "default") - assert.NilError(t, err) - c, err := ioutil.ReadFile(filepath.Join(d, ConfigFileName)) - assert.NilError(t, err) - assert.Equal(t, string(c), "{}") -} diff --git a/api/config/keys.go b/api/config/keys.go deleted file mode 100644 index 5ecbf3a36..000000000 --- a/api/config/keys.go +++ /dev/null @@ -1,32 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package config - -const ( - // ConfigFileName is the name of config file - ConfigFileName = "config.json" - // ConfigFileDir is the default folder where the config file is stored - ConfigFileDir = ".docker" - // ConfigFlagName is the name of the config flag - ConfigFlagName = "config" -) - -const ( - // currentContextKey is the key used in the Docker config file to set the - // default context - currentContextKey = "currentContext" -) diff --git a/api/containers/api.go b/api/containers/api.go deleted file mode 100644 index 02cab49d1..000000000 --- a/api/containers/api.go +++ /dev/null @@ -1,181 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package containers - -import ( - "context" - "io" - - "github.com/compose-spec/compose-go/types" - specs "github.com/opencontainers/image-spec/specs-go/v1" - - "github.com/docker/compose-cli/utils" -) - -const ( - // RestartPolicyNone Never restarts - RestartPolicyNone = "none" - // RestartPolicyAny Always restarts - RestartPolicyAny = "any" - // RestartPolicyOnFailure Restarts only on failure - RestartPolicyOnFailure = "on-failure" - - // RestartPolicyRunNo Always restarts - RestartPolicyRunNo = "no" - // RestartPolicyRunAlways Always restarts - RestartPolicyRunAlways = "always" -) - -// Container represents a created container -type Container struct { - ID string - Status string - Image string - Command string - CPUTime uint64 - MemoryUsage uint64 - PidsCurrent uint64 - PidsLimit uint64 - Config *RuntimeConfig `json:",omitempty"` - HostConfig *HostConfig `json:",omitempty"` - Ports []Port `json:",omitempty"` - Platform string - Healthcheck Healthcheck -} - -// RuntimeConfig config of a created container -type RuntimeConfig struct { - Labels []string `json:",omitempty"` - Env map[string]string `json:",omitempty"` - // FQDN is the fqdn to use - FQDN string `json:"fqdn,omitempty"` -} - -// HostConfig config of the container host -type HostConfig struct { - RestartPolicy string - CPUReservation float64 - CPULimit float64 - MemoryReservation uint64 - MemoryLimit uint64 - AutoRemove bool -} - -// Port represents a published port of a container -type Port struct { - // HostPort is the port number on the host - HostPort uint32 - // ContainerPort is the port number inside the container - ContainerPort uint32 - // Protocol is the protocol of the port mapping - Protocol string - // HostIP is the host ip to use - HostIP string -} - -// ContainerConfig contains the configuration data about a container -type ContainerConfig struct { - // ID uniquely identifies the container - ID string - // Image specifies the image reference used for a container - Image string - // Command are the arguments passed to the container's entrypoint - Command []string - // Ports provide a list of published ports - Ports []Port - // Labels set labels to the container - Labels map[string]string - // Volumes to be mounted - Volumes []string - // Memlimit - MemLimit utils.MemBytes - // CPUlimit - CPULimit float64 - // Environment variables - Environment []string - // Restart policy condition - RestartPolicyCondition string - // DomainName Container NIS domain name - DomainName string - // AutoRemove sets the container to be removed automatically when stopped - AutoRemove bool - // Healthcheck contains the command and interval of the checks - Healthcheck Healthcheck - // Platform contains the platform information - Platform *specs.Platform -} - -// Healthcheck defines the configuration of a healthcheck -type Healthcheck struct { - // Disable disables the check - Disable bool - // Test is the command to be run to check the health of the container - Test []string - // Interval is the period in between the checks - Interval types.Duration - // Retries is the number of attempts before declaring the container as healthy or unhealthy - Retries int - // StartPeriod is the start delay before starting the checks - StartPeriod types.Duration - // Timeout is the timeout in between checks - Timeout types.Duration -} - -// ExecRequest contaiens configuration about an exec request -type ExecRequest struct { - Stdin io.Reader - Stdout io.Writer - Stderr io.Writer - Command string - Interactive bool - Tty bool -} - -// LogsRequest contains configuration about a log request -type LogsRequest struct { - Follow bool - Tail string - Width int - Writer io.Writer -} - -// DeleteRequest contains configuration about a delete request -type DeleteRequest struct { - Force bool -} - -// Service interacts with the underlying container backend -type Service interface { - // List returns all the containers - List(ctx context.Context, all bool) ([]Container, error) - // Start starts a stopped container - Start(ctx context.Context, containerID string) error - // Stop stops the running container - Stop(ctx context.Context, containerID string, timeout *uint32) error - // Kill stops the running container - Kill(ctx context.Context, containerID string, signal string) error - // Run creates and starts a container - Run(ctx context.Context, config ContainerConfig) error - // Exec executes a command inside a running container - Exec(ctx context.Context, containerName string, request ExecRequest) error - // Logs returns all the logs of a container - Logs(ctx context.Context, containerName string, request LogsRequest) error - // Delete removes containers - Delete(ctx context.Context, containerID string, request DeleteRequest) error - // Inspect get a specific container - Inspect(ctx context.Context, id string) (Container, error) -} diff --git a/api/context/context.go b/api/context/context.go deleted file mode 100644 index 5d8577a4f..000000000 --- a/api/context/context.go +++ /dev/null @@ -1,29 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package context - -var current string - -// WithCurrentContext sets the name of the current docker context -func WithCurrentContext(contextName string) { - current = contextName -} - -// Current returns the current context name -func Current() string { - return current -} diff --git a/api/context/flags.go b/api/context/flags.go deleted file mode 100644 index ab545d3a5..000000000 --- a/api/context/flags.go +++ /dev/null @@ -1,34 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package context - -import ( - "os" - - "github.com/spf13/pflag" -) - -// ContextFlags are the global CLI flags -// nolint stutter -type ContextFlags struct { - Context string -} - -// AddContextFlags adds persistent (global) flags -func (c *ContextFlags) AddContextFlags(flags *pflag.FlagSet) { - flags.StringVarP(&c.Context, "context", "c", os.Getenv("DOCKER_CONTEXT"), "context") -} diff --git a/api/context/store/contextmetadata.go b/api/context/store/contextmetadata.go deleted file mode 100644 index 2ecba7855..000000000 --- a/api/context/store/contextmetadata.go +++ /dev/null @@ -1,113 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package store - -import "encoding/json" - -// DockerContext represents the docker context metadata -type DockerContext struct { - Name string `json:",omitempty"` - Metadata ContextMetadata `json:",omitempty"` - Endpoints map[string]interface{} `json:",omitempty"` -} - -// Type the context type -func (m *DockerContext) Type() string { - if m.Metadata.Type == "" { - return DefaultContextType - } - return m.Metadata.Type -} - -// ContextMetadata is represtentation of the data we put in a context -// metadata -type ContextMetadata struct { - Type string - Description string - StackOrchestrator string - AdditionalFields map[string]interface{} -} - -// AciContext is the context for the ACI backend -type AciContext struct { - SubscriptionID string `json:",omitempty"` - Location string `json:",omitempty"` - ResourceGroup string `json:",omitempty"` -} - -// EcsContext is the context for the AWS backend -type EcsContext struct { - CredentialsFromEnv bool `json:",omitempty"` - Profile string `json:",omitempty"` -} - -// KubeContext is the context for a kube backend -type KubeContext struct { - ContextName string `json:",omitempty"` - KubeconfigPath string `json:",omitempty"` - FromEnvironment bool -} - -// AwsContext is the context for the ecs plugin -type AwsContext EcsContext - -// LocalContext is the context for the local backend -type LocalContext struct{} - -// MarshalJSON implements custom JSON marshalling -func (dc ContextMetadata) MarshalJSON() ([]byte, error) { - s := map[string]interface{}{} - if dc.Description != "" { - s["Description"] = dc.Description - } - if dc.StackOrchestrator != "" { - s["StackOrchestrator"] = dc.StackOrchestrator - } - if dc.Type != "" { - s["Type"] = dc.Type - } - if dc.AdditionalFields != nil { - for k, v := range dc.AdditionalFields { - s[k] = v - } - } - return json.Marshal(s) -} - -// UnmarshalJSON implements custom JSON marshalling -func (dc *ContextMetadata) UnmarshalJSON(payload []byte) error { - var data map[string]interface{} - if err := json.Unmarshal(payload, &data); err != nil { - return err - } - for k, v := range data { - switch k { - case "Description": - dc.Description = v.(string) - case "StackOrchestrator": - dc.StackOrchestrator = v.(string) - case "Type": - dc.Type = v.(string) - default: - if dc.AdditionalFields == nil { - dc.AdditionalFields = make(map[string]interface{}) - } - dc.AdditionalFields[k] = v - } - } - return nil -} diff --git a/api/context/store/contextmetadata_test.go b/api/context/store/contextmetadata_test.go deleted file mode 100644 index 951695487..000000000 --- a/api/context/store/contextmetadata_test.go +++ /dev/null @@ -1,46 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package store - -import ( - "encoding/json" - "testing" - - "gotest.tools/v3/assert" -) - -func TestDockerContextMetadataKeepAdditionalFields(t *testing.T) { - c := ContextMetadata{ - Description: "test", - Type: "aci", - StackOrchestrator: "swarm", - AdditionalFields: map[string]interface{}{ - "foo": "bar", - }, - } - jsonBytes, err := json.Marshal(c) - assert.NilError(t, err) - assert.Equal(t, string(jsonBytes), `{"Description":"test","StackOrchestrator":"swarm","Type":"aci","foo":"bar"}`) - - var c2 ContextMetadata - err = json.Unmarshal(jsonBytes, &c2) - assert.NilError(t, err) - assert.Equal(t, c2.AdditionalFields["foo"], "bar") - assert.Equal(t, c2.Type, "aci") - assert.Equal(t, c2.StackOrchestrator, "swarm") - assert.Equal(t, c2.Description, "test") -} diff --git a/api/context/store/store.go b/api/context/store/store.go deleted file mode 100644 index 6f3cc80d4..000000000 --- a/api/context/store/store.go +++ /dev/null @@ -1,336 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package store - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "reflect" - - "github.com/docker/compose-cli/pkg/api" - - "github.com/opencontainers/go-digest" - "github.com/pkg/errors" -) - -const ( - // DefaultContextName is an automatically generated local context - DefaultContextName = "default" - // DefaultContextType is the type for all moby contexts (not associated with cli backend) - DefaultContextType = "moby" - - // AwsContextType is the type for aws contexts (currently a CLI plugin, not associated with cli backend) - // to be removed with the cli plugin - AwsContextType = "aws" - - // EcsContextType is the endpoint key in the context endpoints for an ECS - // backend - EcsContextType = "ecs" - - // EcsLocalSimulationContextType is the endpoint key in the context endpoints for an ECS backend - // running local simulation endpoints - EcsLocalSimulationContextType = "ecs-local" - - // AciContextType is the endpoint key in the context endpoints for an ACI - // backend - AciContextType = "aci" - // LocalContextType is the endpoint key in the context endpoints for a new - // local backend - LocalContextType = "local" - // KubeContextType is the endpoint key in the context endpoints for a new - // kube backend - KubeContextType = "kube" -) - -const ( - dockerEndpointKey = "docker" - contextsDir = "contexts" - metadataDir = "meta" - metaFile = "meta.json" -) - -var instance Store - -// WithContextStore adds the store to the context -func WithContextStore(store Store) { - instance = store -} - -// Instance returns the store from the context -func Instance() Store { - return instance -} - -// Store is the context store -type Store interface { - // Get returns the context with name, it returns an error if the context - // doesn't exist - Get(name string) (*DockerContext, error) - // GetEndpoint sets the `v` parameter to the value of the endpoint for a - // particular context type - GetEndpoint(name string, v interface{}) error - // Create creates a new context, it returns an error if a context with the - // same name exists already. - Create(name string, contextType string, description string, data interface{}) error - // List returns the list of created contexts - List() ([]*DockerContext, error) - // Remove removes a context by name from the context store - Remove(name string) error - // ContextExists checks if a context already exists - ContextExists(name string) bool -} - -// Endpoint holds the Docker or the Kubernetes endpoint, they both have the -// `Host` property, only kubernetes will have the `DefaultNamespace` -type Endpoint struct { - Host string `json:",omitempty"` - DefaultNamespace string `json:",omitempty"` -} - -type store struct { - root string -} - -// New returns a configured context store with specified root dir (eg. $HOME/.docker) as root -func New(rootDir string) (Store, error) { - s := &store{ - root: rootDir, - } - - m := filepath.Join(s.root, contextsDir, metadataDir) - if err := createDirIfNotExist(m); err != nil { - return nil, err - } - - return s, nil -} - -// Get returns the context with the given name -func (s *store) Get(name string) (*DockerContext, error) { - if name == "default" { - return dockerDefaultContext() - } - meta := filepath.Join(s.root, contextsDir, metadataDir, contextDirOf(name), metaFile) - m, err := read(meta) - if os.IsNotExist(err) { - return nil, errors.Wrap(api.ErrNotFound, objectName(name)) - } else if err != nil { - return nil, err - } - - return m, nil -} - -func (s *store) GetEndpoint(name string, data interface{}) error { - meta, err := s.Get(name) - if err != nil { - return err - } - contextType := meta.Type() - if _, ok := meta.Endpoints[contextType]; !ok { - return errors.Wrapf(api.ErrNotFound, "endpoint of type %q", contextType) - } - - dstPtrValue := reflect.ValueOf(data) - dstValue := reflect.Indirect(dstPtrValue) - - val := reflect.ValueOf(meta.Endpoints[contextType]) - valIndirect := reflect.Indirect(val) - - if dstValue.Type() != valIndirect.Type() { - return api.ErrWrongContextType - } - - dstValue.Set(valIndirect) - - return nil -} - -func read(meta string) (*DockerContext, error) { - bytes, err := ioutil.ReadFile(meta) - if err != nil { - return nil, err - } - - var metadata DockerContext - if err := json.Unmarshal(bytes, &metadata); err != nil { - return nil, err - } - - metadata.Endpoints, err = toTypedEndpoints(metadata.Endpoints) - if err != nil { - return nil, err - } - - return &metadata, nil -} - -func toTypedEndpoints(endpoints map[string]interface{}) (map[string]interface{}, error) { - result := map[string]interface{}{} - for k, v := range endpoints { - bytes, err := json.Marshal(v) - if err != nil { - return nil, err - } - typeGetters := getters() - typeGetter, ok := typeGetters[k] - if !ok { - typeGetter = func() interface{} { - return &Endpoint{} - } - } - - val := typeGetter() - err = json.Unmarshal(bytes, &val) - if err != nil { - return nil, err - } - - result[k] = val - } - - return result, nil -} - -func (s *store) ContextExists(name string) bool { - if name == DefaultContextName { - return true - } - dir := contextDirOf(name) - metaDir := filepath.Join(s.root, contextsDir, metadataDir, dir) - if _, err := os.Stat(metaDir); !os.IsNotExist(err) { - return true - } - return false -} - -func (s *store) Create(name string, contextType string, description string, data interface{}) error { - if s.ContextExists(name) { - return errors.Wrap(api.ErrAlreadyExists, objectName(name)) - } - dir := contextDirOf(name) - metaDir := filepath.Join(s.root, contextsDir, metadataDir, dir) - - err := os.Mkdir(metaDir, 0755) - if err != nil { - return err - } - - meta := DockerContext{ - Name: name, - Metadata: ContextMetadata{ - Type: contextType, - Description: description, - }, - Endpoints: map[string]interface{}{ - (dockerEndpointKey): data, - (contextType): data, - }, - } - - bytes, err := json.Marshal(&meta) - if err != nil { - return err - } - - return ioutil.WriteFile(filepath.Join(metaDir, metaFile), bytes, 0644) -} - -func (s *store) List() ([]*DockerContext, error) { - root := filepath.Join(s.root, contextsDir, metadataDir) - c, err := ioutil.ReadDir(root) - if err != nil { - return nil, err - } - - var result []*DockerContext - for _, fi := range c { - if fi.IsDir() { - meta := filepath.Join(root, fi.Name(), metaFile) - r, err := read(meta) - if err != nil { - return nil, err - } - result = append(result, r) - } - } - - // The default context is not stored in the store, it is in-memory only - // so we need a special case for it. - dockerDefault, err := dockerDefaultContext() - if err != nil { - return nil, err - } - - result = append(result, dockerDefault) - return result, nil -} - -func (s *store) Remove(name string) error { - if name == DefaultContextName { - return errors.Wrap(api.ErrForbidden, objectName(name)) - } - dir := filepath.Join(s.root, contextsDir, metadataDir, contextDirOf(name)) - // Check if directory exists because os.RemoveAll returns nil if it doesn't - if _, err := os.Stat(dir); os.IsNotExist(err) { - return errors.Wrap(api.ErrNotFound, objectName(name)) - } - if err := os.RemoveAll(dir); err != nil { - return errors.Wrapf(api.ErrUnknown, "unable to remove %s: %s", objectName(name), err) - } - return nil -} - -func contextDirOf(name string) string { - return digest.FromString(name).Encoded() -} - -func objectName(name string) string { - return fmt.Sprintf("context %q", name) -} - -func createDirIfNotExist(dir string) error { - if _, err := os.Stat(dir); os.IsNotExist(err) { - if err = os.MkdirAll(dir, 0755); err != nil { - return err - } - } - return nil -} - -// Different context types managed by the store. -// TODO(rumpl): we should make this extensible in the future if we want to -// be able to manage other contexts. -func getters() map[string]func() interface{} { - return map[string]func() interface{}{ - AciContextType: func() interface{} { - return &AciContext{} - }, - EcsContextType: func() interface{} { - return &EcsContext{} - }, - LocalContextType: func() interface{} { - return &LocalContext{} - }, - KubeContextType: func() interface{} { - return &KubeContext{} - }, - } -} diff --git a/api/context/store/store_test.go b/api/context/store/store_test.go deleted file mode 100644 index 54fc6a25b..000000000 --- a/api/context/store/store_test.go +++ /dev/null @@ -1,121 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package store - -import ( - _ "crypto/sha256" - "io/ioutil" - "os" - "testing" - - "github.com/docker/compose-cli/pkg/api" - - "gotest.tools/v3/assert" - "gotest.tools/v3/assert/cmp" -) - -func testStore(t *testing.T) Store { - d, err := ioutil.TempDir("", "store") - assert.NilError(t, err) - - t.Cleanup(func() { - _ = os.RemoveAll(d) - }) - - s, err := New(d) - assert.NilError(t, err) - - return s -} - -func TestCreate(t *testing.T) { - s := testStore(t) - err := s.Create("test", "test", "description", ContextMetadata{}) - assert.NilError(t, err) - - err = s.Create("test", "test", "descrsiption", ContextMetadata{}) - assert.Error(t, err, `context "test": already exists`) - assert.Assert(t, api.IsAlreadyExistsError(err)) -} - -func TestGetEndpoint(t *testing.T) { - s := testStore(t) - err := s.Create("aci", "aci", "description", AciContext{ - Location: "eu", - }) - assert.NilError(t, err) - - var ctx AciContext - err = s.GetEndpoint("aci", &ctx) - assert.NilError(t, err) - assert.Equal(t, ctx.Location, "eu") - - var localCtx LocalContext - err = s.GetEndpoint("aci", &localCtx) - assert.Error(t, err, "wrong context type") -} - -func TestGetUnknown(t *testing.T) { - s := testStore(t) - meta, err := s.Get("unknown") - assert.Assert(t, cmp.Nil(meta)) - assert.Error(t, err, `context "unknown": not found`) - assert.Assert(t, api.IsNotFoundError(err)) -} - -func TestGet(t *testing.T) { - s := testStore(t) - err := s.Create("test", "type", "description", ContextMetadata{}) - assert.NilError(t, err) - - meta, err := s.Get("test") - assert.NilError(t, err) - assert.Assert(t, meta != nil) - var m DockerContext - if meta != nil { - m = *meta - } - - assert.Equal(t, m.Name, "test") - assert.Equal(t, m.Metadata.Description, "description") - assert.Equal(t, m.Type(), "type") -} - -func TestRemoveNotFound(t *testing.T) { - s := testStore(t) - err := s.Remove("notfound") - assert.Error(t, err, `context "notfound": not found`) - assert.Assert(t, api.IsNotFoundError(err)) -} - -func TestRemove(t *testing.T) { - s := testStore(t) - err := s.Create("testremove", "type", "description", ContextMetadata{}) - assert.NilError(t, err) - - meta, err := s.Get("testremove") - assert.NilError(t, err) - assert.Assert(t, meta != nil) - - err = s.Remove("testremove") - assert.NilError(t, err) - - meta, err = s.Get("testremove") - assert.Error(t, err, `context "testremove": not found`) - assert.Assert(t, cmp.Nil(meta)) - -} diff --git a/api/context/store/storedefault.go b/api/context/store/storedefault.go deleted file mode 100644 index 1cfa8d57f..000000000 --- a/api/context/store/storedefault.go +++ /dev/null @@ -1,91 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package store - -import ( - "bytes" - "encoding/json" - "os/exec" - - "github.com/pkg/errors" -) - -// Represents a context as created by the docker cli -type defaultContext struct { - Metadata ContextMetadata - Endpoints endpoints -} - -// Normally (in docker/cli code), the endpoints are mapped as map[string]interface{} -// but docker cli contexts always have a "docker" and "kubernetes" key so we -// create real types for those to no have to juggle around with interfaces. -type endpoints struct { - Docker endpoint `json:"docker,omitempty"` - Kubernetes endpoint `json:"kubernetes,omitempty"` -} - -// Both "docker" and "kubernetes" endpoints in the docker cli created contexts -// have a "Host", only kubernetes has the "DefaultNamespace", we put both of -// those here for easier manipulation and to not have to create two distinct -// structs -type endpoint struct { - Host string - DefaultNamespace string -} - -func dockerDefaultContext() (*DockerContext, error) { - // ensure we run this using default context, in current context has been damaged / removed in store - cmd := exec.Command("com.docker.cli", "--context", "default", "context", "inspect", "default") - var stdout bytes.Buffer - cmd.Stdout = &stdout - err := cmd.Run() - if err != nil { - return nil, err - } - - var ctx []defaultContext - err = json.Unmarshal(stdout.Bytes(), &ctx) - if err != nil { - return nil, err - } - - if len(ctx) != 1 { - return nil, errors.New("found more than one default context") - } - - defaultCtx := ctx[0] - - meta := DockerContext{ - Name: "default", - Endpoints: map[string]interface{}{ - "docker": &Endpoint{ - Host: defaultCtx.Endpoints.Docker.Host, - }, - "kubernetes": &Endpoint{ - Host: defaultCtx.Endpoints.Kubernetes.Host, - DefaultNamespace: defaultCtx.Endpoints.Kubernetes.DefaultNamespace, - }, - }, - Metadata: ContextMetadata{ - Type: DefaultContextType, - Description: "Current DOCKER_HOST based configuration", - StackOrchestrator: defaultCtx.Metadata.StackOrchestrator, - }, - } - - return &meta, nil -} diff --git a/api/resources/api.go b/api/resources/api.go deleted file mode 100644 index 729acf1a1..000000000 --- a/api/resources/api.go +++ /dev/null @@ -1,39 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package resources - -import ( - "context" -) - -// PruneRequest options on what to prune -type PruneRequest struct { - Force bool - DryRun bool -} - -// PruneResult info on what has been pruned -type PruneResult struct { - DeletedIDs []string - Summary string -} - -// Service interacts with the underlying container backend -type Service interface { - // Prune prune resources - Prune(ctx context.Context, request PruneRequest) (PruneResult, error) -} diff --git a/api/secrets/api.go b/api/secrets/api.go deleted file mode 100644 index bf3315e14..000000000 --- a/api/secrets/api.go +++ /dev/null @@ -1,60 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package secrets - -import ( - "context" - "encoding/json" -) - -// Service interacts with the underlying secrets backend -type Service interface { - CreateSecret(ctx context.Context, secret Secret) (string, error) - InspectSecret(ctx context.Context, id string) (Secret, error) - ListSecrets(ctx context.Context) ([]Secret, error) - DeleteSecret(ctx context.Context, id string, recover bool) error -} - -// Secret hold sensitive data -type Secret struct { - ID string `json:"ID"` - Name string `json:"Name"` - Labels map[string]string `json:"Tags"` - content []byte -} - -// NewSecret builds a secret -func NewSecret(name string, content []byte) Secret { - return Secret{ - Name: name, - content: content, - } -} - -// ToJSON marshall a Secret into JSON string -func (s Secret) ToJSON() (string, error) { - b, err := json.MarshalIndent(&s, "", "\t") - if err != nil { - return "", err - } - return string(b), nil -} - -// GetContent returns a Secret's sensitive data -func (s Secret) GetContent() []byte { - return s.content -} diff --git a/api/volumes/api.go b/api/volumes/api.go deleted file mode 100644 index 22f21dfa7..000000000 --- a/api/volumes/api.go +++ /dev/null @@ -1,39 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package volumes - -import ( - "context" -) - -// Volume volume info -type Volume struct { - ID string - Description string -} - -// Service interacts with the underlying container backend -type Service interface { - // List returns all available volumes - List(ctx context.Context) ([]Volume, error) - // Create creates a new volume - Create(ctx context.Context, name string, options interface{}) (Volume, error) - // Delete deletes an existing volume - Delete(ctx context.Context, volumeID string, options interface{}) error - // Inspect inspects an existing volume - Inspect(ctx context.Context, volumeID string) (Volume, error) -} diff --git a/builder.Makefile b/builder.Makefile index c15f175be..3e3fc8aab 100644 --- a/builder.Makefile +++ b/builder.Makefile @@ -31,9 +31,6 @@ GIT_TAG?=$(shell git describe --tags --match "v[0-9]*") LDFLAGS="-s -w -X $(PKG_NAME)/internal.Version=${GIT_TAG}" GO_BUILD=$(STATIC_FLAGS) go build -trimpath -ldflags=$(LDFLAGS) -BINARY?=bin/docker -BINARY_WITH_EXTENSION=$(BINARY)$(EXTENSION) - COMPOSE_BINARY?=bin/docker-compose COMPOSE_BINARY_WITH_EXTENSION=$(COMPOSE_BINARY)$(EXTENSION) @@ -45,31 +42,12 @@ ifdef BUILD_TAGS LINT_TAGS=--build-tags $(BUILD_TAGS) endif -.PHONY: protos -protos: - protoc -I. --go_out=plugins=grpc,paths=source_relative:. ${PROTOS} - -.PHONY: cli -cli: - GOOS=${GOOS} GOARCH=${GOARCH} $(GO_BUILD) $(TAGS) -o $(BINARY_WITH_EXTENSION) ./cli - .PHONY: compose-plugin compose-plugin: GOOS=${GOOS} GOARCH=${GOARCH} $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY_WITH_EXTENSION) ./cmd .PHONY: cross cross: - GOOS=linux GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(BINARY)-linux-amd64 ./cli - GOOS=linux GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(BINARY)-linux-arm64 ./cli - GOOS=linux GOARM=6 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(BINARY)-linux-armv6 ./cli - GOOS=linux GOARM=7 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(BINARY)-linux-armv7 ./cli - GOOS=linux GOARCH=s390x $(GO_BUILD) $(TAGS) -o $(BINARY)-linux-s390x ./cli - GOOS=darwin GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(BINARY)-darwin-amd64 ./cli - GOOS=darwin GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(BINARY)-darwin-arm64 ./cli - GOOS=windows GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(BINARY)-windows-amd64.exe ./cli - -.PHONY: cross-compose-plugin -cross-compose-plugin: GOOS=linux GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-amd64 ./cmd GOOS=linux GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-arm64 ./cmd GOOS=linux GOARM=6 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-armv6 ./cmd @@ -87,11 +65,7 @@ test: lint: golangci-lint run $(LINT_TAGS) --timeout 10m0s ./... -.PHONY: import-restrictions -import-restrictions: - import-restrictions --configuration import-restrictions.yaml - -.PHONY: check-licese-headers +.PHONY: check-license-headers check-license-headers: ./scripts/validate/fileheader diff --git a/cli/cmd/context/context.go b/cli/cmd/context/context.go deleted file mode 100644 index 53feffaaf..000000000 --- a/cli/cmd/context/context.go +++ /dev/null @@ -1,67 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package context - -import ( - "github.com/docker/compose-cli/cli/mobycli" - "github.com/spf13/cobra" -) - -// Command manages contexts -func Command() *cobra.Command { - cmd := &cobra.Command{ - Use: "context", - Short: "Manage contexts", - } - - cmd.AddCommand( - createCommand(), - listCommand(), - removeCommand(), - showCommand(), - useCommand(), - inspectCommand(), - updateCommand(), - exportCommand(), - importCommand(), - ) - - return cmd -} - -func exportCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "export", - Short: "Export a context to a tar or kubeconfig file", - Run: func(cmd *cobra.Command, args []string) { - mobycli.Exec(cmd.Root()) - }, - } - cmd.Flags().Bool("kubeconfig", false, "Export as a kubeconfig file") - return cmd -} - -func importCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "import", - Short: "Import a context from a tar or zip file", - Run: func(cmd *cobra.Command, args []string) { - mobycli.Exec(cmd.Root()) - }, - } - return cmd -} diff --git a/cli/cmd/context/create.go b/cli/cmd/context/create.go deleted file mode 100644 index b2dcd3ca6..000000000 --- a/cli/cmd/context/create.go +++ /dev/null @@ -1,131 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package context - -import ( - "fmt" - "strings" - - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/cli/mobycli" -) - -type descriptionCreateOpts struct { - description string -} - -var extraCommands []func() *cobra.Command -var extraHelp []string - -func createCommand() *cobra.Command { - help := strings.Join(extraHelp, "\n") - - longHelp := fmt.Sprintf(`Create a new context - -Create docker engine context: -$ docker context create CONTEXT [flags] - -%s - -Docker endpoint config: - -NAME DESCRIPTION -from Copy named context's Docker endpoint configuration -host Docker endpoint on which to connect -ca Trust certs signed only by this CA -cert Path to TLS certificate file -key Path to TLS key file -skip-tls-verify Skip TLS certificate validation - -Kubernetes endpoint config: - -NAME DESCRIPTION -from Copy named context's Kubernetes endpoint configuration -config-file Path to a Kubernetes config file -context-override Overrides the context set in the kubernetes config file -namespace-override Overrides the namespace set in the kubernetes config file - -Example: - -$ docker context create my-context --description "some description" --docker "host=tcp://myserver:2376,ca=~/ca-file,cert=~/cert-file,key=~/key-file"`, help) - - cmd := &cobra.Command{ - Use: "create CONTEXT", - Short: "Create new context", - RunE: func(cmd *cobra.Command, args []string) error { - mobycli.Exec(cmd.Root()) - return nil - }, - Long: longHelp, - } - - cmd.AddCommand( - createLocalCommand(), - ) - for _, command := range extraCommands { - cmd.AddCommand(command()) - } - - flags := cmd.Flags() - flags.String("description", "", "Description of the context") - flags.String( - "default-stack-orchestrator", "", - "Default orchestrator for stack operations to use with this context (swarm|kubernetes|all)") - flags.StringToString("docker", nil, "Set the docker endpoint") - flags.StringToString("kubernetes", nil, "Set the kubernetes endpoint") - flags.String("from", "", "Create context from a named context") - - return cmd -} - -func createLocalCommand() *cobra.Command { - var opts descriptionCreateOpts - cmd := &cobra.Command{ - Use: "local CONTEXT", - Short: "Create a context for accessing local engine", - Args: cobra.ExactArgs(1), - Hidden: true, - RunE: func(cmd *cobra.Command, args []string) error { - return createDockerContext(args[0], store.LocalContextType, opts.description, store.LocalContext{}) - }, - } - addDescriptionFlag(cmd, &opts.description) - return cmd -} - -func createDockerContext(name string, contextType string, description string, data interface{}) error { - s := store.Instance() - result := s.Create( - name, - contextType, - description, - data, - ) - fmt.Printf("Successfully created %s context %q\n", contextType, name) - return result -} - -func contextExists(name string) bool { - s := store.Instance() - return s.ContextExists(name) -} - -func addDescriptionFlag(cmd *cobra.Command, descriptionOpt *string) { - cmd.Flags().StringVar(descriptionOpt, "description", "", "Description of the context") -} diff --git a/cli/cmd/context/create_aci.go b/cli/cmd/context/create_aci.go deleted file mode 100644 index 0a06a24fc..000000000 --- a/cli/cmd/context/create_aci.go +++ /dev/null @@ -1,80 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package context - -import ( - "context" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/aci" - "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/pkg/api" -) - -func init() { - extraCommands = append(extraCommands, createAciCommand) - extraHelp = append(extraHelp, ` -Create Azure Container Instances context: -$ docker context create aci CONTEXT [flags] -(see docker context create aci --help) -`) -} - -func createAciCommand() *cobra.Command { - var opts aci.ContextParams - cmd := &cobra.Command{ - Use: "aci CONTEXT [flags]", - Short: "Create a context for Azure Container Instances", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - return runCreateAci(cmd.Context(), args[0], opts) - }, - } - - addDescriptionFlag(cmd, &opts.Description) - cmd.Flags().StringVar(&opts.Location, "location", "", "Location") - cmd.Flags().StringVar(&opts.SubscriptionID, "subscription-id", "", "Subscription id") - cmd.Flags().StringVar(&opts.ResourceGroup, "resource-group", "", "Resource group") - - return cmd -} - -func runCreateAci(ctx context.Context, contextName string, opts aci.ContextParams) error { - if contextExists(contextName) { - return errors.Wrapf(api.ErrAlreadyExists, "context %s", contextName) - } - contextData, description, err := getAciContextData(ctx, opts) - if err != nil { - if aci.IsSubscriptionNotFoundError(err) { - return errors.New("could not find the requested subscription from your Azure login. You might need to specify a tenant ID with docker login azure --tenant-id xxx") - } - return err - } - return createDockerContext(contextName, store.AciContextType, description, contextData) - -} - -func getAciContextData(ctx context.Context, opts aci.ContextParams) (interface{}, string, error) { - cs, err := client.GetCloudService(ctx, store.AciContextType) - if err != nil { - return nil, "", errors.Wrap(err, "cannot connect to ACI backend") - } - return cs.CreateContextData(ctx, opts) -} diff --git a/cli/cmd/context/create_ecs.go b/cli/cmd/context/create_ecs.go deleted file mode 100644 index eabf24e41..000000000 --- a/cli/cmd/context/create_ecs.go +++ /dev/null @@ -1,144 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package context - -import ( - "bufio" - "context" - "fmt" - "os" - "strings" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/ecs" - "github.com/docker/compose-cli/pkg/api" -) - -func init() { - extraCommands = append(extraCommands, createEcsCommand) - extraHelp = append(extraHelp, ` -Create Amazon ECS context: -$ docker context create ecs CONTEXT [flags] -(see docker context create ecs --help) -`) -} - -func createEcsCommand() *cobra.Command { - var localSimulation bool - var opts ecs.ContextParams - var accessKeysFile string - cmd := &cobra.Command{ - Use: "ecs CONTEXT [flags]", - Short: "Create a context for Amazon ECS", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - opts.Name = args[0] - if accessKeysFile != "" { - err := parseAccessKeysFile(accessKeysFile, &opts) - if err != nil { - return err - } - } - - if opts.CredsFromEnv && opts.Profile != "" { - return fmt.Errorf("--profile and --from-env flags cannot be set at the same time") - } - if accessKeysFile != "" && opts.Profile != "" { - return fmt.Errorf("--profile and --access-keys flags cannot be set at the same time") - } - if opts.CredsFromEnv && accessKeysFile != "" { - return fmt.Errorf("--access-keys and --from-env flags cannot be set at the same time") - } - if localSimulation { - return runCreateLocalSimulation(cmd.Context(), args[0], opts) - } - return runCreateEcs(cmd.Context(), args[0], opts) - }, - } - - addDescriptionFlag(cmd, &opts.Description) - cmd.Flags().BoolVar(&localSimulation, "local-simulation", false, "Create context for ECS local simulation endpoints") - cmd.Flags().StringVar(&opts.Profile, "profile", "", "Use an existing AWS profile") - cmd.Flags().StringVar(&accessKeysFile, "access-keys", "", "Use AWS access keys from file") - cmd.Flags().BoolVar(&opts.CredsFromEnv, "from-env", false, "Use AWS environment variables for profile, or credentials and region") - return cmd -} - -func parseAccessKeysFile(file string, opts *ecs.ContextParams) error { - f, err := os.Open(file) - if err != nil { - return err - } - defer f.Close() // nolint:errcheck - scanner := bufio.NewScanner(f) - scanner.Split(bufio.ScanLines) - values := map[string]string{} - for scanner.Scan() { - line := scanner.Text() - parts := strings.SplitN(line, "=", 2) - values[parts[0]] = parts[1] - } - var ok bool - opts.AccessKey, ok = values["AWSAccessKeyId"] - if !ok { - return fmt.Errorf("%s is missing AWSAccessKeyId", file) - } - opts.SecretKey, ok = values["AWSSecretKey"] - if !ok { - return fmt.Errorf("%s is missing AWSSecretKey", file) - } - return nil -} - -func runCreateLocalSimulation(ctx context.Context, contextName string, opts ecs.ContextParams) error { - if contextExists(contextName) { - return errors.Wrapf(api.ErrAlreadyExists, "context %q", contextName) - } - cs, err := client.GetCloudService(ctx, store.EcsLocalSimulationContextType) - if err != nil { - return errors.Wrap(err, "cannot connect to ECS backend") - } - data, description, err := cs.CreateContextData(ctx, opts) - if err != nil { - return err - } - return createDockerContext(contextName, store.EcsLocalSimulationContextType, description, data) -} - -func runCreateEcs(ctx context.Context, contextName string, opts ecs.ContextParams) error { - if contextExists(contextName) { - return errors.Wrapf(api.ErrAlreadyExists, "context %q", contextName) - } - contextData, description, err := getEcsContextData(ctx, opts) - if err != nil { - return err - } - return createDockerContext(contextName, store.EcsContextType, description, contextData) - -} - -func getEcsContextData(ctx context.Context, opts ecs.ContextParams) (interface{}, string, error) { - cs, err := client.GetCloudService(ctx, store.EcsContextType) - if err != nil { - return nil, "", errors.Wrap(err, "cannot connect to ECS backend") - } - return cs.CreateContextData(ctx, opts) -} diff --git a/cli/cmd/context/create_kube.go b/cli/cmd/context/create_kube.go deleted file mode 100644 index 3f24abac2..000000000 --- a/cli/cmd/context/create_kube.go +++ /dev/null @@ -1,67 +0,0 @@ -// +build kube - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package context - -import ( - "github.com/docker/compose-cli/pkg/api" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/kube" -) - -func init() { - extraCommands = append(extraCommands, createKubeCommand) - extraHelp = append(extraHelp, ` -Create a Kubernetes context: -$ docker context create kubernetes CONTEXT [flags] -(see docker context create kubernetes --help) -`) -} - -func createKubeCommand() *cobra.Command { - var opts kube.ContextParams - cmd := &cobra.Command{ - Use: "kubernetes CONTEXT [flags]", - Short: "Create context for a Kubernetes Cluster", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - return runCreateKube(args[0], opts) - }, - } - - addDescriptionFlag(cmd, &opts.Description) - cmd.Flags().StringVar(&opts.KubeConfigPath, "kubeconfig", "", "The endpoint of the Kubernetes manager") - cmd.Flags().StringVar(&opts.KubeContextName, "kubecontext", "", "The name of the context to use in kubeconfig") - cmd.Flags().BoolVar(&opts.FromEnvironment, "from-env", false, "Get endpoint and creds from env vars") - return cmd -} - -func runCreateKube(contextName string, opts kube.ContextParams) error { - if contextExists(contextName) { - return errors.Wrapf(api.ErrAlreadyExists, "context %q", contextName) - } - - contextData, description, err := opts.CreateContextData() - if err != nil { - return err - } - return createDockerContext(contextName, store.KubeContextType, description, contextData) -} diff --git a/cli/cmd/context/inspect.go b/cli/cmd/context/inspect.go deleted file mode 100644 index c2550ca75..000000000 --- a/cli/cmd/context/inspect.go +++ /dev/null @@ -1,38 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package context - -import ( - "github.com/docker/compose-cli/cli/mobycli" - - "github.com/spf13/cobra" -) - -func inspectCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "inspect", - Short: "Display detailed information on one or more contexts", - RunE: func(cmd *cobra.Command, args []string) error { - mobycli.Exec(cmd.Root()) - return nil - }, - } - // flags matching delegated command in moby cli - flags := cmd.Flags() - flags.StringP("format", "f", "", "Format the output using the given Go template") - return cmd -} diff --git a/cli/cmd/context/ls.go b/cli/cmd/context/ls.go deleted file mode 100644 index a83d8519b..000000000 --- a/cli/cmd/context/ls.go +++ /dev/null @@ -1,164 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package context - -import ( - "fmt" - "io" - "os" - "sort" - "strings" - - formatter2 "github.com/docker/compose-cli/cmd/formatter" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - apicontext "github.com/docker/compose-cli/api/context" - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/cli/mobycli" -) - -type lsOpts struct { - quiet bool - json bool - format string -} - -func (o lsOpts) validate() error { - if o.quiet && o.json { - return errors.New(`cannot combine "quiet" and "json" options`) - } - return nil -} - -func listCommand() *cobra.Command { - var opts lsOpts - cmd := &cobra.Command{ - Use: "list", - Short: "List available contexts", - Aliases: []string{"ls"}, - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - return runList(cmd, opts) - }, - } - cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Only show context names") - cmd.Flags().StringVar(&opts.format, "format", "", "Format the output. Values: [pretty | json]. (Default: pretty)") - - return cmd -} - -func runList(cmd *cobra.Command, opts lsOpts) error { - err := opts.validate() - if err != nil { - return err - } - format := strings.ToLower(strings.ReplaceAll(opts.format, " ", "")) - if format != "" && format != formatter2.JSON && format != formatter2.PRETTY && format != formatter2.TemplateLegacyJSON { - mobycli.Exec(cmd.Root()) - return nil - } - - currentContext := apicontext.Current() - s := store.Instance() - contexts, err := s.List() - if err != nil { - return err - } - - sort.Slice(contexts, func(i, j int) bool { - return strings.Compare(contexts[i].Name, contexts[j].Name) == -1 - }) - - if opts.quiet { - for _, c := range contexts { - fmt.Println(c.Name) - } - return nil - } - - if opts.json || format == formatter2.JSON { - opts.format = formatter2.JSON - } - if format == formatter2.TemplateLegacyJSON { - opts.format = formatter2.TemplateLegacyJSON - } - - view := viewFromContextList(contexts, currentContext) - return formatter2.Print(view, opts.format, os.Stdout, - func(w io.Writer) { - for _, c := range view { - contextName := c.Name - if c.Current { - contextName += " *" - } - _, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", - contextName, - c.ContextType, - c.Description, - c.DockerEndpoint, - c.KubernetesEndpoint, - c.StackOrchestrator) - } - }, - "NAME", "TYPE", "DESCRIPTION", "DOCKER ENDPOINT", "KUBERNETES ENDPOINT", "ORCHESTRATOR") -} - -func getEndpoint(name string, meta map[string]interface{}) string { - endpoints, ok := meta[name] - if !ok { - return "" - } - data, ok := endpoints.(*store.Endpoint) - if !ok { - return "" - } - - result := data.Host - if data.DefaultNamespace != "" { - result += fmt.Sprintf(" (%s)", data.DefaultNamespace) - } - - return result -} - -type contextView struct { - Current bool - Description string - DockerEndpoint string - KubernetesEndpoint string - ContextType string - Name string - StackOrchestrator string -} - -func viewFromContextList(contextList []*store.DockerContext, currentContext string) []contextView { - retList := make([]contextView, len(contextList)) - for i, c := range contextList { - retList[i] = contextView{ - Current: c.Name == currentContext, - Description: c.Metadata.Description, - DockerEndpoint: getEndpoint("docker", c.Endpoints), - KubernetesEndpoint: getEndpoint("kubernetes", c.Endpoints), - Name: c.Name, - ContextType: c.Type(), - StackOrchestrator: c.Metadata.StackOrchestrator, - } - } - return retList -} diff --git a/cli/cmd/context/rm.go b/cli/cmd/context/rm.go deleted file mode 100644 index 1602c9d4e..000000000 --- a/cli/cmd/context/rm.go +++ /dev/null @@ -1,83 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package context - -import ( - "errors" - "fmt" - - "github.com/docker/compose-cli/cmd/formatter" - - "github.com/hashicorp/go-multierror" - "github.com/spf13/cobra" - - apicontext "github.com/docker/compose-cli/api/context" - "github.com/docker/compose-cli/api/context/store" -) - -type removeOpts struct { - force bool -} - -func removeCommand() *cobra.Command { - var opts removeOpts - cmd := &cobra.Command{ - Use: "rm CONTEXT [CONTEXT...]", - Short: "Remove one or more contexts", - Aliases: []string{"remove"}, - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - return runRemove(args, opts.force) - }, - } - cmd.Flags().BoolVarP(&opts.force, "force", "f", false, "Force removing current context") - - return cmd -} - -func runRemove(args []string, force bool) error { - currentContext := apicontext.Current() - s := store.Instance() - - var errs *multierror.Error - for _, contextName := range args { - if currentContext == contextName { - if force { - if err := runUse("default"); err != nil { - errs = multierror.Append(errs, errors.New("cannot delete current context")) - } else { - errs = removeContext(s, contextName, errs) - } - } else { - errs = multierror.Append(errs, errors.New("cannot delete current context")) - } - } else { - errs = removeContext(s, contextName, errs) - } - } - formatter.SetMultiErrorFormat(errs) - return errs.ErrorOrNil() -} - -func removeContext(s store.Store, n string, errs *multierror.Error) *multierror.Error { - if err := s.Remove(n); err != nil { - errs = multierror.Append(errs, err) - } else { - fmt.Println(n) - } - return errs -} diff --git a/cli/cmd/context/show.go b/cli/cmd/context/show.go deleted file mode 100644 index 8ba588a5d..000000000 --- a/cli/cmd/context/show.go +++ /dev/null @@ -1,50 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package context - -import ( - "fmt" - - "github.com/spf13/cobra" - - apicontext "github.com/docker/compose-cli/api/context" - "github.com/docker/compose-cli/api/context/store" -) - -func showCommand() *cobra.Command { - return &cobra.Command{ - Use: "show", - Short: "Print the current context", - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - return runShow() - }, - } -} - -func runShow() error { - name := apicontext.Current() - // Match behavior of existing CLI - if name != store.DefaultContextName { - s := store.Instance() - if _, err := s.Get(name); err != nil { - return err - } - } - fmt.Println(name) - return nil -} diff --git a/cli/cmd/context/update.go b/cli/cmd/context/update.go deleted file mode 100644 index e71bc105c..000000000 --- a/cli/cmd/context/update.go +++ /dev/null @@ -1,85 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package context - -import ( - "github.com/docker/compose-cli/pkg/api" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/cli/mobycli" -) - -func updateCommand() *cobra.Command { - - longHelp := `Update a context - -Docker endpoint config: - -NAME DESCRIPTION -from Copy named context's Docker endpoint configuration -host Docker endpoint on which to connect -ca Trust certs signed only by this CA -cert Path to TLS certificate file -key Path to TLS key file -skip-tls-verify Skip TLS certificate validation - -Kubernetes endpoint config: - -NAME DESCRIPTION -from Copy named context's Kubernetes endpoint configuration -config-file Path to a Kubernetes config file -context-override Overrides the context set in the kubernetes config file -namespace-override Overrides the namespace set in the kubernetes config file - -Example: - -$ docker context update my-context --description "some description" --docker "host=tcp://myserver:2376,ca=~/ca-file,cert=~/cert-file,key=~/key-file"` - - cmd := &cobra.Command{ - Use: "update", - Short: "Update a context", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - return runUpdate(cmd, args[0]) - }, - Long: longHelp, - } - flags := cmd.Flags() - flags.String("description", "", "Description of the context") - flags.String( - "default-stack-orchestrator", "", - "Default orchestrator for stack operations to use with this context (swarm|kubernetes|all)") - flags.StringToString("docker", nil, "Set the docker endpoint") - flags.StringToString("kubernetes", nil, "Set the kubernetes endpoint") - - return cmd -} - -func runUpdate(cmd *cobra.Command, name string) error { - s := store.Instance() - dockerContext, err := s.Get(name) - if err == nil && dockerContext != nil { - if dockerContext.Type() != store.DefaultContextType { - return errors.Wrapf(api.ErrNotImplemented, "context update for context type %q not supported", dockerContext.Type()) - } - } - - mobycli.Exec(cmd.Root()) - return nil -} diff --git a/cli/cmd/context/use.go b/cli/cmd/context/use.go deleted file mode 100644 index 5e8bbfd6a..000000000 --- a/cli/cmd/context/use.go +++ /dev/null @@ -1,52 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package context - -import ( - "fmt" - - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/api/config" - "github.com/docker/compose-cli/api/context/store" -) - -func useCommand() *cobra.Command { - return &cobra.Command{ - Use: "use CONTEXT", - Short: "Set the default context", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - return runUse(args[0]) - }, - } -} - -func runUse(name string) error { - s := store.Instance() - // Match behavior of existing CLI - if name != store.DefaultContextName { - if _, err := s.Get(name); err != nil { - return err - } - } - if err := config.WriteCurrentContext(config.Dir(), name); err != nil { - return err - } - fmt.Println(name) - return nil -} diff --git a/cli/cmd/ecs.go b/cli/cmd/ecs.go deleted file mode 100644 index d5b2d9722..000000000 --- a/cli/cmd/ecs.go +++ /dev/null @@ -1,35 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package cmd - -import ( - "errors" - - "github.com/spf13/cobra" -) - -// EcsCommand is a placeholder to drive early users to the integrated form of ecs support instead of its early plugin form -func EcsCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "ecs", - RunE: func(cmd *cobra.Command, args []string) error { - return errors.New("The ECS integration is now part of the CLI. Use `docker compose` with an ECS context.") // nolint - }, - } - - return cmd -} diff --git a/cli/cmd/exec.go b/cli/cmd/exec.go deleted file mode 100644 index 5901e83f8..000000000 --- a/cli/cmd/exec.go +++ /dev/null @@ -1,88 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package cmd - -import ( - "context" - "fmt" - "os" - "strings" - - "github.com/containerd/console" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/api/containers" -) - -type execOpts struct { - tty bool - interactive bool -} - -// ExecCommand runs a command in a running container -func ExecCommand() *cobra.Command { - var opts execOpts - cmd := &cobra.Command{ - Use: "exec", - Short: "Run a command in a running container", - Args: cobra.MinimumNArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { - return runExec(cmd.Context(), opts, args[0], strings.Join(args[1:], " ")) - }, - } - - cmd.Flags().BoolVarP(&opts.tty, "tty", "t", false, "Allocate a pseudo-TTY") - cmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", false, "Keep STDIN open even if not attached") - - return cmd -} - -func runExec(ctx context.Context, opts execOpts, name string, command string) error { - c, err := client.New(ctx) - if err != nil { - return errors.Wrap(err, "cannot connect to backend") - } - - request := containers.ExecRequest{ - Command: command, - Tty: opts.tty, - Interactive: opts.interactive, - Stdin: os.Stdin, - Stdout: os.Stdout, - Stderr: os.Stderr, - } - - if opts.tty { - con := console.Current() - if err := con.SetRaw(); err != nil { - return err - } - defer func() { - if err := con.Reset(); err != nil { - fmt.Println("Unable to close the console") - } - }() - - request.Stdin = con - request.Stdout = con - request.Stderr = con - } - - return c.ContainerService().Exec(ctx, name, request) -} diff --git a/cli/cmd/inspect.go b/cli/cmd/inspect.go deleted file mode 100644 index 8f84f714e..000000000 --- a/cli/cmd/inspect.go +++ /dev/null @@ -1,140 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package cmd - -import ( - "context" - "fmt" - - "github.com/docker/compose-cli/cmd/formatter" - - "github.com/Azure/go-autorest/autorest/to" - "github.com/compose-spec/compose-go/types" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/api/containers" -) - -// InspectCommand inspects into containers -func InspectCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "inspect", - Short: "Inspect containers", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - return runInspect(cmd.Context(), args[0]) - }, - } - - return cmd -} - -func runInspect(ctx context.Context, id string) error { - c, err := client.New(ctx) - if err != nil { - return errors.Wrap(err, "cannot connect to backend") - } - - container, err := c.ContainerService().Inspect(ctx, id) - if err != nil { - return err - } - - view := getInspectView(container) - - j, err := formatter.ToStandardJSON(view) - if err != nil { - return err - } - fmt.Print(j) - - return nil -} - -// ContainerInspectView inspect view -type ContainerInspectView struct { - ID string - Status string - Image string - Command string `json:",omitempty"` - HostConfig *containers.HostConfig `json:",omitempty"` - Ports []containers.Port `json:",omitempty"` - Config *containers.RuntimeConfig `json:",omitempty"` - Platform string - Healthcheck *containerInspectHealthcheck `json:",omitempty"` -} - -type containerInspectHealthcheck struct { - // Test is the command to be run to check the health of the container - Test []string `json:",omitempty"` - // Interval is the period in between the checks - Interval *types.Duration `json:",omitempty"` - // Retries is the number of attempts before declaring the container as healthy or unhealthy - Retries *int `json:",omitempty"` - // StartPeriod is the start delay before starting the checks - StartPeriod *types.Duration `json:",omitempty"` - // Timeout is the timeout in between checks - Timeout *types.Duration `json:",omitempty"` -} - -func getInspectView(container containers.Container) ContainerInspectView { - var ( - healthcheck *containerInspectHealthcheck - test []string - retries *int - ports []containers.Port - ) - - if len(container.Ports) > 0 { - ports = container.Ports - } - if !container.Healthcheck.Disable && len(container.Healthcheck.Test) > 0 { - test = container.Healthcheck.Test - if container.Healthcheck.Retries != 0 { - retries = to.IntPtr(container.Healthcheck.Retries) - } - getDurationPtr := func(d types.Duration) *types.Duration { - if d == types.Duration(0) { - return nil - } - return &d - } - - healthcheck = &containerInspectHealthcheck{ - Test: test, - Retries: retries, - Interval: getDurationPtr(container.Healthcheck.Interval), - StartPeriod: getDurationPtr(container.Healthcheck.StartPeriod), - Timeout: getDurationPtr(container.Healthcheck.Timeout), - } - } - - return ContainerInspectView{ - ID: container.ID, - Status: container.Status, - Image: container.Image, - Command: container.Command, - - Config: container.Config, - HostConfig: container.HostConfig, - Ports: ports, - Platform: container.Platform, - Healthcheck: healthcheck, - } -} diff --git a/cli/cmd/kill.go b/cli/cmd/kill.go deleted file mode 100644 index be4671521..000000000 --- a/cli/cmd/kill.go +++ /dev/null @@ -1,75 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package cmd - -import ( - "context" - "fmt" - - "github.com/docker/compose-cli/cmd/formatter" - - "github.com/hashicorp/go-multierror" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/pkg/api" -) - -type killOpts struct { - signal string -} - -// KillCommand kills containers -func KillCommand() *cobra.Command { - var opts killOpts - cmd := &cobra.Command{ - Use: "kill", - Short: "Kill one or more running containers", - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - return runKill(cmd.Context(), args, opts) - }, - } - flags := cmd.Flags() - flags.StringVarP(&opts.signal, "signal", "s", "KILL", "Signal to send to the container") - - return cmd -} - -func runKill(ctx context.Context, args []string, opts killOpts) error { - c, err := client.New(ctx) - if err != nil { - return errors.Wrap(err, "cannot connect to backend") - } - - var errs *multierror.Error - for _, id := range args { - err := c.ContainerService().Kill(ctx, id, opts.signal) - if err != nil { - if api.IsNotFoundError(err) { - errs = multierror.Append(errs, fmt.Errorf("container %s not found", id)) - } else { - errs = multierror.Append(errs, err) - } - continue - } - fmt.Println(id) - } - formatter.SetMultiErrorFormat(errs) - return errs.ErrorOrNil() -} diff --git a/cli/cmd/login/azurelogin.go b/cli/cmd/login/azurelogin.go deleted file mode 100644 index 93e9b8158..000000000 --- a/cli/cmd/login/azurelogin.go +++ /dev/null @@ -1,46 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package login - -import ( - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/aci" -) - -// AzureLoginCommand returns the azure login command -func AzureLoginCommand() *cobra.Command { - opts := aci.LoginParams{} - cmd := &cobra.Command{ - Use: "azure", - Short: "Log in to azure", - Args: cobra.MaximumNArgs(0), - RunE: func(cmd *cobra.Command, args []string) error { - if err := opts.Validate(); err != nil { - return err - } - return cloudLogin(cmd, "aci", opts) - }, - } - flags := cmd.Flags() - flags.StringVar(&opts.TenantID, "tenant-id", "", "Specify tenant ID to use") - flags.StringVar(&opts.ClientID, "client-id", "", "Client ID for Service principal login") - flags.StringVar(&opts.ClientSecret, "client-secret", "", "Client secret for Service principal login") - flags.StringVar(&opts.CloudName, "cloud-name", "", "Name of a registered Azure cloud [AzureCloud | AzureChinaCloud | AzureGermanCloud | AzureUSGovernment] (AzureCloud by default)") - - return cmd -} diff --git a/cli/cmd/login/login.go b/cli/cmd/login/login.go deleted file mode 100644 index d74504828..000000000 --- a/cli/cmd/login/login.go +++ /dev/null @@ -1,70 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package login - -import ( - "context" - "fmt" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/cli/mobycli" - "github.com/docker/compose-cli/pkg/api" -) - -// Command returns the login command -func Command() *cobra.Command { - cmd := &cobra.Command{ - Use: "login [OPTIONS] [SERVER]", - Short: "Log in to a Docker registry or cloud backend", - Long: "Log in to a Docker registry or cloud backend.\nIf no registry server is specified, the default is defined by the daemon.", - Args: cobra.MaximumNArgs(1), - RunE: runLogin, - } - // define flags for backward compatibility with com.docker.cli - flags := cmd.Flags() - flags.StringP("username", "u", "", "username") - flags.StringP("password", "p", "", "password") - flags.BoolP("password-stdin", "", false, "Take the password from stdin") - - cmd.AddCommand(AzureLoginCommand()) - return cmd -} - -func runLogin(cmd *cobra.Command, args []string) error { - mobycli.Exec(cmd.Root()) - return nil -} - -func cloudLogin(cmd *cobra.Command, backendType string, params interface{}) error { - ctx := cmd.Context() - cs, err := client.GetCloudService(ctx, backendType) - if err != nil { - return errors.Wrap(api.ErrLoginFailed, "cannot connect to backend") - } - err = cs.Login(ctx, params) - if errors.Is(err, context.Canceled) { - return errors.New("login canceled") - } - if err != nil { - return err - } - fmt.Println("login succeeded") - return nil -} diff --git a/cli/cmd/logout/azure.go b/cli/cmd/logout/azure.go deleted file mode 100644 index 0b998357e..000000000 --- a/cli/cmd/logout/azure.go +++ /dev/null @@ -1,58 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package logout - -import ( - "context" - "fmt" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/pkg/api" -) - -// AzureLogoutCommand returns the azure logout command -func AzureLogoutCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "azure", - Short: "Logout from Azure", - Args: cobra.MaximumNArgs(0), - RunE: func(cmd *cobra.Command, args []string) error { - return cloudLogout(cmd, "aci") - }, - } - return cmd -} - -func cloudLogout(cmd *cobra.Command, backendType string) error { - ctx := cmd.Context() - cs, err := client.GetCloudService(ctx, backendType) - if err != nil { - return errors.Wrap(api.ErrLoginFailed, "cannot connect to backend") - } - err = cs.Logout(ctx) - if errors.Is(err, context.Canceled) { - return errors.New("logout canceled") - } - if err != nil { - return err - } - fmt.Println("Removing login credentials for Azure") - return nil -} diff --git a/cli/cmd/logout/logout.go b/cli/cmd/logout/logout.go deleted file mode 100644 index 621f75036..000000000 --- a/cli/cmd/logout/logout.go +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package logout - -import ( - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/cli/mobycli" -) - -// Command returns the login command -func Command() *cobra.Command { - cmd := &cobra.Command{ - Use: "logout [SERVER]", - Short: "Log out from a Docker registry or cloud backend", - Long: "Log out from a Docker registry or cloud backend.\nIf no server is specified, the default is defined by the daemon.", - Args: cobra.MaximumNArgs(1), - RunE: runLogout, - } - - cmd.AddCommand(AzureLogoutCommand()) - return cmd -} - -func runLogout(cmd *cobra.Command, args []string) error { - mobycli.Exec(cmd.Root()) - return nil -} diff --git a/cli/cmd/logs.go b/cli/cmd/logs.go deleted file mode 100644 index 2b09cc161..000000000 --- a/cli/cmd/logs.go +++ /dev/null @@ -1,79 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package cmd - -import ( - "context" - "io" - "os" - - "github.com/containerd/console" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/api/containers" -) - -type logsOpts struct { - Follow bool - Tail string -} - -// LogsCommand fetches and shows logs of a container -func LogsCommand() *cobra.Command { - var opts logsOpts - cmd := &cobra.Command{ - Use: "logs", - Short: "Fetch the logs of a container", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - return runLogs(cmd.Context(), args[0], opts) - }, - } - - cmd.Flags().BoolVarP(&opts.Follow, "follow", "f", false, "Follow log outut") - cmd.Flags().StringVar(&opts.Tail, "tail", "all", "Number of lines to show from the end of the logs") - - return cmd -} - -func runLogs(ctx context.Context, containerName string, opts logsOpts) error { - c, err := client.New(ctx) - if err != nil { - return errors.Wrap(err, "cannot connect to backend") - } - - req := containers.LogsRequest{ - Follow: opts.Follow, - Tail: opts.Tail, - } - - var con io.Writer = os.Stdout - if c, err := console.ConsoleFromFile(os.Stdout); err == nil { - size, err := c.Size() - if err != nil { - return err - } - req.Width = int(size.Width) - con = c - } - - req.Writer = con - - return c.ContainerService().Logs(ctx, containerName, req) -} diff --git a/cli/cmd/metadata.go b/cli/cmd/metadata.go deleted file mode 100644 index 8b4867b1e..000000000 --- a/cli/cmd/metadata.go +++ /dev/null @@ -1,57 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package cmd - -import ( - "encoding/json" - "fmt" - - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/internal" -) - -// BackendMetadata backend metadata -// TODO import this from cli when merged & available in /docker/cli -type BackendMetadata struct { - Name string - Version string -} - -// MetadataCommand command to display version -func MetadataCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "backend-metadata", - Short: "return CLI backend metadata", - Args: cobra.MaximumNArgs(0), - Hidden: true, - RunE: func(cmd *cobra.Command, _ []string) error { - metadata := BackendMetadata{ - Name: "Cloud integration", - Version: internal.Version, - } - jsonMeta, err := json.Marshal(metadata) - if err != nil { - return err - } - fmt.Println(string(jsonMeta)) - return nil - }, - } - - return cmd -} diff --git a/cli/cmd/prune.go b/cli/cmd/prune.go deleted file mode 100644 index 5b6710d44..000000000 --- a/cli/cmd/prune.go +++ /dev/null @@ -1,76 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package cmd - -import ( - "context" - "fmt" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/api/resources" -) - -type pruneOpts struct { - force bool - dryRun bool -} - -// PruneCommand deletes backend resources -func PruneCommand() *cobra.Command { - var opts pruneOpts - cmd := &cobra.Command{ - Use: "prune", - Short: "prune existing resources in current context", - Args: cobra.MaximumNArgs(0), - RunE: func(cmd *cobra.Command, args []string) error { - return runPrune(cmd.Context(), opts) - }, - } - - cmd.Flags().BoolVar(&opts.force, "force", false, "Also prune running containers and Compose applications") - cmd.Flags().BoolVar(&opts.dryRun, "dry-run", false, "List resources to be deleted, but do not delete them") - - return cmd -} - -func runPrune(ctx context.Context, opts pruneOpts) error { - c, err := client.New(ctx) - if err != nil { - return errors.Wrap(err, "cannot connect to backend") - } - - result, err := c.ResourceService().Prune(ctx, resources.PruneRequest{Force: opts.force, DryRun: opts.dryRun}) - if err != nil { - return err - } - deletedResourcesMsg := "Deleted resources:" - if opts.dryRun { - deletedResourcesMsg = "Resources that would be deleted:" - } - fmt.Println(deletedResourcesMsg) - - for _, id := range result.DeletedIDs { - fmt.Println(id) - } - if result.Summary != "" { - fmt.Println(result.Summary) - } - return err -} diff --git a/cli/cmd/ps.go b/cli/cmd/ps.go deleted file mode 100644 index 940a6b147..000000000 --- a/cli/cmd/ps.go +++ /dev/null @@ -1,132 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package cmd - -import ( - "context" - "fmt" - "io" - "os" - "strings" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/api/containers" - format "github.com/docker/compose-cli/cmd/formatter" - "github.com/docker/compose-cli/utils/formatter" -) - -type psOpts struct { - all bool - quiet bool - json bool - format string -} - -// PsCommand lists containers -func PsCommand() *cobra.Command { - var opts psOpts - cmd := &cobra.Command{ - Use: "ps", - Short: "List containers", - RunE: func(cmd *cobra.Command, args []string) error { - return runPs(cmd.Context(), opts) - }, - } - - cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs") - cmd.Flags().BoolVarP(&opts.all, "all", "a", false, "Show all containers (default shows just running)") - cmd.Flags().BoolVar(&opts.json, "json", false, "Format output as JSON") - cmd.Flags().StringVar(&opts.format, "format", "", "Format the output. Values: [pretty | json]. (Default: pretty)") - _ = cmd.Flags().MarkHidden("json") // Legacy. This is used by VSCode Docker extension - - return cmd -} - -func (o psOpts) validate() error { - if o.quiet && o.json { - return errors.New(`cannot combine "quiet" and "json" options`) - } - return nil -} - -func runPs(ctx context.Context, opts psOpts) error { - err := opts.validate() - if err != nil { - return err - } - c, err := client.New(ctx) - if err != nil { - return errors.Wrap(err, "cannot connect to backend") - } - - containerList, err := c.ContainerService().List(ctx, opts.all) - if err != nil { - return errors.Wrap(err, "fetch containers") - } - - if opts.quiet { - for _, c := range containerList { - fmt.Println(c.ID) - } - return nil - } - - if opts.json { - opts.format = format.JSON - } - - view := viewFromContainerList(containerList) - return format.Print(view, opts.format, os.Stdout, func(w io.Writer) { - for _, c := range view { - _, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", c.ID, c.Image, c.Command, c.Status, - strings.Join(c.Ports, ", ")) - } - }, "CONTAINER ID", "IMAGE", "COMMAND", "STATUS", "PORTS") -} - -func fqdn(container containers.Container) string { - fqdn := "" - if container.Config != nil { - fqdn = container.Config.FQDN - } - return fqdn -} - -type containerView struct { - ID string - Image string - Status string - Command string - Ports []string -} - -func viewFromContainerList(containerList []containers.Container) []containerView { - retList := make([]containerView, len(containerList)) - for i, c := range containerList { - retList[i] = containerView{ - ID: c.ID, - Image: c.Image, - Status: c.Status, - Command: c.Command, - Ports: formatter.PortsToStrings(c.Ports, fqdn(c)), - } - } - return retList -} diff --git a/cli/cmd/rm.go b/cli/cmd/rm.go deleted file mode 100644 index 5f012e1f5..000000000 --- a/cli/cmd/rm.go +++ /dev/null @@ -1,81 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package cmd - -import ( - "context" - "fmt" - - "github.com/docker/compose-cli/cmd/formatter" - - "github.com/hashicorp/go-multierror" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/api/containers" - "github.com/docker/compose-cli/pkg/api" -) - -type rmOpts struct { - force bool -} - -// RmCommand deletes containers -func RmCommand() *cobra.Command { - var opts rmOpts - cmd := &cobra.Command{ - Use: "rm", - Short: "Remove containers", - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - return runRm(cmd.Context(), args, opts) - }, - } - - cmd.Flags().BoolVarP(&opts.force, "force", "f", false, "Force removal") - - return cmd -} - -func runRm(ctx context.Context, args []string, opts rmOpts) error { - c, err := client.New(ctx) - if err != nil { - return errors.Wrap(err, "cannot connect to backend") - } - - var errs *multierror.Error - for _, id := range args { - err := c.ContainerService().Delete(ctx, id, containers.DeleteRequest{ - Force: opts.force, - }) - if err != nil { - if api.IsForbiddenError(err) { - errs = multierror.Append(errs, fmt.Errorf("you cannot remove a running container %s. Stop the container before attempting removal or force remove", id)) - } else if api.IsNotFoundError(err) { - errs = multierror.Append(errs, fmt.Errorf("container %s not found", id)) - } else { - errs = multierror.Append(errs, err) - } - continue - } - - fmt.Println(id) - } - formatter.SetMultiErrorFormat(errs) - return errs.ErrorOrNil() -} diff --git a/cli/cmd/run/run.go b/cli/cmd/run/run.go deleted file mode 100644 index 3e83376af..000000000 --- a/cli/cmd/run/run.go +++ /dev/null @@ -1,134 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package run - -import ( - "context" - "fmt" - "io" - "os" - "time" - - "github.com/containerd/console" - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/api/containers" - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/cli/options/run" - "github.com/docker/compose-cli/pkg/progress" -) - -// Command runs a container -func Command(contextType string) *cobra.Command { - var opts run.Opts - cmd := &cobra.Command{ - Use: "run", - Short: "Run a container", - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) > 1 { - opts.Command = args[1:] - } - return runRun(cmd.Context(), args[0], contextType, opts) - }, - } - cmd.Flags().SetInterspersed(false) - - cmd.Flags().StringArrayVarP(&opts.Publish, "publish", "p", []string{}, "Publish a container's port(s). [HOST_PORT:]CONTAINER_PORT") - cmd.Flags().StringVar(&opts.Name, "name", "", "Assign a name to the container") - cmd.Flags().StringArrayVarP(&opts.Labels, "label", "l", []string{}, "Set meta data on a container") - cmd.Flags().StringArrayVarP(&opts.Volumes, "volume", "v", []string{}, "Volume. Ex: storageaccount/my_share[:/absolute/path/to/target][:ro]") - cmd.Flags().BoolVarP(&opts.Detach, "detach", "d", false, "Run container in background and print container ID") - cmd.Flags().Float64Var(&opts.Cpus, "cpus", 1., "Number of CPUs") - cmd.Flags().VarP(&opts.Memory, "memory", "m", "Memory limit") - cmd.Flags().StringArrayVarP(&opts.Environment, "env", "e", []string{}, "Set environment variables") - cmd.Flags().StringArrayVar(&opts.EnvironmentFiles, "env-file", []string{}, "Path to environment files to be translated as environment variables") - cmd.Flags().StringVarP(&opts.RestartPolicyCondition, "restart", "", containers.RestartPolicyRunNo, "Restart policy to apply when a container exits (no|always|on-failure)") - cmd.Flags().BoolVar(&opts.Rm, "rm", false, "Automatically remove the container when it exits") - cmd.Flags().StringVar(&opts.HealthCmd, "health-cmd", "", "Command to run to check health") - cmd.Flags().DurationVar(&opts.HealthInterval, "health-interval", time.Duration(0), "Time between running the check (ms|s|m|h) (default 0s)") - cmd.Flags().IntVar(&opts.HealthRetries, "health-retries", 0, "Consecutive failures needed to report unhealthy") - cmd.Flags().DurationVar(&opts.HealthStartPeriod, "health-start-period", time.Duration(0), "Start period for the container to initialize before starting "+ - "health-retries countdown (ms|s|m|h) (default 0s)") - cmd.Flags().DurationVar(&opts.HealthTimeout, "health-timeout", time.Duration(0), "Maximum time to allow one check to run (ms|s|m|h) (default 0s)") - - if contextType == store.LocalContextType { - cmd.Flags().StringVar(&opts.Platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable") - } - - if contextType == store.AciContextType { - cmd.Flags().StringVar(&opts.DomainName, "domainname", "", "Container NIS domain name") - } - - switch contextType { - case store.LocalContextType: - default: - _ = cmd.Flags().MarkHidden("rm") - } - - return cmd -} - -func runRun(ctx context.Context, image string, contextType string, opts run.Opts) error { - switch contextType { - case store.LocalContextType: - default: - if opts.Rm { - return fmt.Errorf(`flag "--rm" is not yet implemented for %q context type`, contextType) - } - } - - c, err := client.New(ctx) - if err != nil { - return err - } - - containerConfig, err := opts.ToContainerConfig(image) - if err != nil { - return err - } - - result, err := progress.RunWithStatus(ctx, func(ctx context.Context) (string, error) { - return containerConfig.ID, c.ContainerService().Run(ctx, containerConfig) - }) - if err != nil { - return err - } - - if !opts.Detach { - var con io.Writer = os.Stdout - req := containers.LogsRequest{ - Follow: true, - } - if c, err := console.ConsoleFromFile(os.Stdout); err == nil { - size, err := c.Size() - if err != nil { - return err - } - req.Width = int(size.Width) - con = c - } - - req.Writer = con - - return c.ContainerService().Logs(ctx, opts.Name, req) - } - - fmt.Println(result) - return nil -} diff --git a/cli/cmd/run/run_test.go b/cli/cmd/run/run_test.go deleted file mode 100644 index 789fe400b..000000000 --- a/cli/cmd/run/run_test.go +++ /dev/null @@ -1,65 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package run - -import ( - "bytes" - "strings" - "testing" - - "gotest.tools/v3/assert" - "gotest.tools/v3/golden" - - "github.com/docker/compose-cli/cli/options/run" -) - -func TestHelp(t *testing.T) { - var b bytes.Buffer - c := Command("aci") - c.SetOutput(&b) - _ = c.Help() - golden.Assert(t, b.String(), "run-help.golden") -} - -func TestHelpNoDomainFlag(t *testing.T) { - var b bytes.Buffer - c := Command("default") - c.SetOutput(&b) - _ = c.Help() - assert.Assert(t, !strings.Contains(b.String(), "domainname")) -} - -func TestRunEnvironmentFiles(t *testing.T) { - runOpts := run.Opts{ - Environment: []string{ - "VAR=1", - }, - EnvironmentFiles: []string{ - "testdata/runtest1.env", - "testdata/runtest2.env", - }, - } - containerConfig, err := runOpts.ToContainerConfig("test") - assert.NilError(t, err) - assert.DeepEqual(t, containerConfig.Environment, []string{ - "VAR=1", - "FIRST_VAR=\"firstValue\"", - "SECOND_VAR=secondValue", - "THIRD_VAR=2", - "FOURTH_VAR=fourthValue", - }) -} diff --git a/cli/cmd/run/testdata/run-help.golden b/cli/cmd/run/testdata/run-help.golden deleted file mode 100644 index a9066ff38..000000000 --- a/cli/cmd/run/testdata/run-help.golden +++ /dev/null @@ -1,22 +0,0 @@ -Run a container - -Usage: - run [flags] - -Flags: - --cpus float Number of CPUs (default 1) - -d, --detach Run container in background and print container ID - --domainname string Container NIS domain name - -e, --env stringArray Set environment variables - --env-file stringArray Path to environment files to be translated as environment variables - --health-cmd string Command to run to check health - --health-interval duration Time between running the check (ms|s|m|h) (default 0s) - --health-retries int Consecutive failures needed to report unhealthy - --health-start-period duration Start period for the container to initialize before starting health-retries countdown (ms|s|m|h) (default 0s) - --health-timeout duration Maximum time to allow one check to run (ms|s|m|h) (default 0s) - -l, --label stringArray Set meta data on a container - -m, --memory bytes Memory limit - --name string Assign a name to the container - -p, --publish stringArray Publish a container's port(s). [HOST_PORT:]CONTAINER_PORT - --restart string Restart policy to apply when a container exits (no|always|on-failure) (default "no") - -v, --volume stringArray Volume. Ex: storageaccount/my_share[:/absolute/path/to/target][:ro] diff --git a/cli/cmd/run/testdata/runtest1.env b/cli/cmd/run/testdata/runtest1.env deleted file mode 100644 index 495661e35..000000000 --- a/cli/cmd/run/testdata/runtest1.env +++ /dev/null @@ -1,2 +0,0 @@ -FIRST_VAR="firstValue" -SECOND_VAR=secondValue diff --git a/cli/cmd/run/testdata/runtest2.env b/cli/cmd/run/testdata/runtest2.env deleted file mode 100644 index d6182fbf9..000000000 --- a/cli/cmd/run/testdata/runtest2.env +++ /dev/null @@ -1,2 +0,0 @@ -THIRD_VAR=2 -FOURTH_VAR=fourthValue diff --git a/cli/cmd/secrets.go b/cli/cmd/secrets.go deleted file mode 100644 index 45360b7dc..000000000 --- a/cli/cmd/secrets.go +++ /dev/null @@ -1,196 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package cmd - -import ( - "fmt" - "io" - "io/ioutil" - "os" - - "github.com/docker/compose-cli/cmd/formatter" - - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/api/secrets" -) - -// SecretCommand manage secrets -func SecretCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "secret", - Short: "Manages secrets", - } - - cmd.AddCommand( - createSecret(), - inspectSecret(), - listSecrets(), - deleteSecret(), - ) - return cmd -} - -func createSecret() *cobra.Command { - cmd := &cobra.Command{ - Use: "create [OPTIONS] SECRET [file|-]", - Short: "Creates a secret.", - Args: cobra.RangeArgs(1, 2), - RunE: func(cmd *cobra.Command, args []string) error { - c, err := client.New(cmd.Context()) - if err != nil { - return err - } - file := "-" - if len(args) == 2 { - file = args[1] - } - if len(file) == 0 { - return fmt.Errorf("secret data source empty: %q", file) - } - var in io.ReadCloser - switch file { - case "-": - in = os.Stdin - default: - in, err = os.Open(file) - if err != nil { - return err - } - defer func() { _ = in.Close() }() - } - content, err := ioutil.ReadAll(in) - if err != nil { - return fmt.Errorf("failed to read content from %q: %v", file, err) - } - name := args[0] - secret := secrets.NewSecret(name, content) - id, err := c.SecretsService().CreateSecret(cmd.Context(), secret) - if err != nil { - return err - } - fmt.Println(id) - return nil - }, - } - return cmd -} - -func inspectSecret() *cobra.Command { - cmd := &cobra.Command{ - Use: "inspect ID", - Short: "Displays secret details", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - c, err := client.New(cmd.Context()) - if err != nil { - return err - } - secret, err := c.SecretsService().InspectSecret(cmd.Context(), args[0]) - if err != nil { - return err - } - out, err := secret.ToJSON() - if err != nil { - return err - } - fmt.Println(out) - return nil - }, - } - return cmd -} - -type listSecretsOpts struct { - format string - quiet bool -} - -func listSecrets() *cobra.Command { - var opts listSecretsOpts - cmd := &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List secrets stored for the existing account.", - RunE: func(cmd *cobra.Command, args []string) error { - c, err := client.New(cmd.Context()) - if err != nil { - return err - } - secretsList, err := c.SecretsService().ListSecrets(cmd.Context()) - if err != nil { - return err - } - if opts.quiet { - for _, s := range secretsList { - fmt.Println(s.ID) - } - return nil - } - view := viewFromSecretList(secretsList) - return formatter.Print(view, opts.format, os.Stdout, func(w io.Writer) { - for _, secret := range view { - _, _ = fmt.Fprintf(w, "%s\t%s\t%s\n", secret.ID, secret.Name, secret.Description) - } - }, "ID", "NAME") - }, - } - cmd.Flags().StringVar(&opts.format, "format", "", "Format the output. Values: [pretty | json]. (Default: pretty)") - cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs") - return cmd -} - -type secretView struct { - ID string - Name string - Description string -} - -func viewFromSecretList(secretList []secrets.Secret) []secretView { - retList := make([]secretView, len(secretList)) - for i, s := range secretList { - retList[i] = secretView{ - ID: s.ID, - Name: s.Name, - } - } - return retList -} - -type deleteSecretOptions struct { - recover bool -} - -func deleteSecret() *cobra.Command { - opts := deleteSecretOptions{} - cmd := &cobra.Command{ - Use: "delete NAME", - Aliases: []string{"rm", "remove"}, - Short: "Removes a secret.", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - c, err := client.New(cmd.Context()) - if err != nil { - return err - } - return c.SecretsService().DeleteSecret(cmd.Context(), args[0], opts.recover) - }, - } - cmd.Flags().BoolVar(&opts.recover, "recover", false, "Enable recovery.") - return cmd -} diff --git a/cli/cmd/serve.go b/cli/cmd/serve.go deleted file mode 100644 index 25714a46a..000000000 --- a/cli/cmd/serve.go +++ /dev/null @@ -1,82 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package cmd - -import ( - "context" - - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/cli/server" - containersv1 "github.com/docker/compose-cli/cli/server/protos/containers/v1" - contextsv1 "github.com/docker/compose-cli/cli/server/protos/contexts/v1" - streamsv1 "github.com/docker/compose-cli/cli/server/protos/streams/v1" - volumesv1 "github.com/docker/compose-cli/cli/server/protos/volumes/v1" - "github.com/docker/compose-cli/cli/server/proxy" -) - -type serveOpts struct { - address string -} - -// ServeCommand returns the command to serve the API -func ServeCommand() *cobra.Command { - // FIXME(chris-crone): Should warn that specified context is ignored - var opts serveOpts - cmd := &cobra.Command{ - Use: "serve", - Short: "Start an api server", - RunE: func(cmd *cobra.Command, args []string) error { - return runServe(cmd.Context(), opts) - }, - } - - cmd.Flags().StringVar(&opts.address, "address", "", "The address to listen to") - - return cmd -} - -func runServe(ctx context.Context, opts serveOpts) error { - s := server.New(ctx) - - listener, err := server.CreateListener(opts.address) - if err != nil { - return errors.Wrap(err, "listen address "+opts.address) - } - // nolint errcheck - defer listener.Close() - - p := proxy.New(ctx) - - containersv1.RegisterContainersServer(s, p) - contextsv1.RegisterContextsServer(s, p.ContextsProxy()) - streamsv1.RegisterStreamingServer(s, p) - volumesv1.RegisterVolumesServer(s, p) - - go func() { - <-ctx.Done() - logrus.Info("stopping server") - s.Stop() - }() - - logrus.WithField("address", opts.address).Info("serving daemon API") - - // start the GRPC server to serve on the listener - return s.Serve(listener) -} diff --git a/cli/cmd/start.go b/cli/cmd/start.go deleted file mode 100644 index a12e4b109..000000000 --- a/cli/cmd/start.go +++ /dev/null @@ -1,68 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package cmd - -import ( - "context" - "fmt" - - "github.com/docker/compose-cli/cmd/formatter" - - "github.com/hashicorp/go-multierror" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/pkg/api" -) - -// StartCommand starts containers -func StartCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "start", - Short: "Start one or more stopped containers", - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - return runStart(cmd.Context(), args) - }, - } - - return cmd -} - -func runStart(ctx context.Context, args []string) error { - c, err := client.New(ctx) - if err != nil { - return errors.Wrap(err, "cannot connect to backend") - } - - var errs *multierror.Error - for _, id := range args { - err := c.ContainerService().Start(ctx, id) - if err != nil { - if api.IsNotFoundError(err) { - errs = multierror.Append(errs, fmt.Errorf("container %s not found", id)) - } else { - errs = multierror.Append(errs, err) - } - continue - } - fmt.Println(id) - } - formatter.SetMultiErrorFormat(errs) - return errs.ErrorOrNil() -} diff --git a/cli/cmd/stop.go b/cli/cmd/stop.go deleted file mode 100644 index e66587ca3..000000000 --- a/cli/cmd/stop.go +++ /dev/null @@ -1,75 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package cmd - -import ( - "context" - "fmt" - - "github.com/docker/compose-cli/cmd/formatter" - - "github.com/hashicorp/go-multierror" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/pkg/api" -) - -type stopOpts struct { - timeout uint32 -} - -// StopCommand deletes containers -func StopCommand() *cobra.Command { - var opts stopOpts - cmd := &cobra.Command{ - Use: "stop", - Short: "Stop one or more running containers", - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - return runStop(cmd.Context(), args, opts) - }, - } - - cmd.Flags().Uint32Var(&opts.timeout, "timeout", 0, "Seconds to wait for stop before killing it (default 0, no timeout)") - - return cmd -} - -func runStop(ctx context.Context, args []string, opts stopOpts) error { - c, err := client.New(ctx) - if err != nil { - return errors.Wrap(err, "cannot connect to backend") - } - - var errs *multierror.Error - for _, id := range args { - err := c.ContainerService().Stop(ctx, id, &opts.timeout) - if err != nil { - if api.IsNotFoundError(err) { - errs = multierror.Append(errs, fmt.Errorf("container %s not found", id)) - } else { - errs = multierror.Append(errs, err) - } - continue - } - fmt.Println(id) - } - formatter.SetMultiErrorFormat(errs) - return errs.ErrorOrNil() -} diff --git a/cli/cmd/testdata/secrets-out.golden b/cli/cmd/testdata/secrets-out.golden deleted file mode 100644 index a902647a9..000000000 --- a/cli/cmd/testdata/secrets-out.golden +++ /dev/null @@ -1,2 +0,0 @@ -ID NAME DESCRIPTION -123 secret123 secret 1,2,3 diff --git a/cli/cmd/version.go b/cli/cmd/version.go deleted file mode 100644 index 4465cb80f..000000000 --- a/cli/cmd/version.go +++ /dev/null @@ -1,122 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package cmd - -import ( - "fmt" - "os" - "strings" - - "github.com/docker/compose-cli/cmd/formatter" - - "github.com/spf13/cobra" - - "github.com/docker/cli/cli" - - "github.com/docker/compose-cli/cli/mobycli" - "github.com/docker/compose-cli/internal" -) - -const formatOpt = "format" - -// VersionCommand command to display version -func VersionCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "version", - Short: "Show the Docker version information", - Args: cobra.MaximumNArgs(0), - RunE: func(cmd *cobra.Command, _ []string) error { - err := runVersion(cmd) - if err != nil { - return cli.StatusError{StatusCode: 1, Status: err.Error()} - } - return nil - }, - } - // define flags for backward compatibility with com.docker.cli - flags := cmd.Flags() - flags.StringP(formatOpt, "f", "", "Format the output. Values: [pretty | json]. (Default: pretty)") - flags.String("kubeconfig", "", "Kubernetes config file") - - return cmd -} - -func runVersion(cmd *cobra.Command) error { - var versionString string - var err error - format := strings.ToLower(strings.ReplaceAll(cmd.Flag(formatOpt).Value.String(), " ", "")) - // Replace is preferred in this case to keep the order. - switch format { - case formatter.PRETTY, "": - versionString, err = getOutFromMoby(cmd, fixedPrettyArgs(os.Args[1:])...) - versionString = strings.Replace(versionString, - "\n Version:", "\n Cloud integration: "+internal.Version+"\n Version:", 1) - case formatter.JSON, formatter.TemplateLegacyJSON: // Try to catch full JSON formats - versionString, err = getOutFromMoby(cmd, fixedJSONArgs(os.Args[1:])...) - versionString = strings.Replace(versionString, - `"Version":`, fmt.Sprintf(`"CloudIntegration":%q,"Version":`, internal.Version), 1) - default: - versionString, err = getOutFromMoby(cmd) - } - - fmt.Print(versionString) - return err -} - -func getOutFromMoby(cmd *cobra.Command, args ...string) (string, error) { - versionResult, err := mobycli.ExecSilent(cmd.Context(), args...) - // we don't want to fail on error, there is an error if the engine is not available but it displays client version info - // Still, technically the [] byte versionResult could be nil, just let the original command display what it has to display - if versionResult == nil { - mobycli.Exec(cmd.Root()) - return "", nil - } - return string(versionResult), err -} - -func fixedPrettyArgs(oArgs []string) []string { - args := make([]string, 0) - for i := 0; i < len(oArgs); i++ { - if isFormatOpt(oArgs[i]) && - len(oArgs) > i && - (strings.ToLower(oArgs[i+1]) == formatter.PRETTY || oArgs[i+1] == "") { - i++ - continue - } - args = append(args, oArgs[i]) - } - return args -} - -func fixedJSONArgs(oArgs []string) []string { - args := make([]string, 0) - for i := 0; i < len(oArgs); i++ { - if isFormatOpt(oArgs[i]) && - len(oArgs) > i && - strings.ToLower(oArgs[i+1]) == formatter.JSON { - args = append(args, oArgs[i], "{{json .}}") - i++ - continue - } - args = append(args, oArgs[i]) - } - return args -} - -func isFormatOpt(o string) bool { - return o == "--format" || o == "-f" -} diff --git a/cli/cmd/version_test.go b/cli/cmd/version_test.go deleted file mode 100644 index bdcf24359..000000000 --- a/cli/cmd/version_test.go +++ /dev/null @@ -1,190 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package cmd - -import ( - "testing" - - "gotest.tools/assert" -) - -type caze struct { - Actual []string - Expected []string -} - -func TestVersionFormat(t *testing.T) { - jsonCases := []caze{ - { - Actual: fixedJSONArgs([]string{}), - Expected: []string{}, - }, - { - Actual: fixedJSONArgs([]string{ - "docker", - "version", - }), - Expected: []string{ - "docker", - "version", - }, - }, - { - Actual: fixedJSONArgs([]string{ - "docker", - "version", - "--format", - "json", - }), - Expected: []string{ - "docker", - "version", - "--format", - "{{json .}}", - }, - }, - { - Actual: fixedJSONArgs([]string{ - "docker", - "version", - "--format", - "jSoN", - }), - Expected: []string{ - "docker", - "version", - "--format", - "{{json .}}", - }, - }, - { - Actual: fixedJSONArgs([]string{ - "docker", - "version", - "--format", - "json", - "--kubeconfig", - "myKubeConfig", - }), - Expected: []string{ - "docker", - "version", - "--format", - "{{json .}}", - "--kubeconfig", - "myKubeConfig", - }, - }, - { - Actual: fixedJSONArgs([]string{ - "--format", - "json", - }), - Expected: []string{ - "--format", - "{{json .}}", - }, - }, - } - prettyCases := []caze{ - { - Actual: fixedPrettyArgs([]string{}), - Expected: []string{}, - }, - { - Actual: fixedPrettyArgs([]string{ - "docker", - "version", - }), - Expected: []string{ - "docker", - "version", - }, - }, - { - Actual: fixedPrettyArgs([]string{ - "docker", - "version", - "--format", - "pretty", - }), - Expected: []string{ - "docker", - "version", - }, - }, - { - Actual: fixedPrettyArgs([]string{ - "docker", - "version", - "--format", - "pRettY", - }), - Expected: []string{ - "docker", - "version", - }, - }, - { - Actual: fixedPrettyArgs([]string{ - "docker", - "version", - "--format", - "", - }), - Expected: []string{ - "docker", - "version", - }, - }, - { - Actual: fixedPrettyArgs([]string{ - "docker", - "version", - "--format", - "pretty", - "--kubeconfig", - "myKubeConfig", - }), - Expected: []string{ - "docker", - "version", - "--kubeconfig", - "myKubeConfig", - }, - }, - { - Actual: fixedPrettyArgs([]string{ - "--format", - "pretty", - }), - Expected: []string{}, - }, - } - - t.Run("json", func(t *testing.T) { - for _, c := range jsonCases { - assert.DeepEqual(t, c.Actual, c.Expected) - } - }) - - t.Run("pretty", func(t *testing.T) { - for _, c := range prettyCases { - assert.DeepEqual(t, c.Actual, c.Expected) - } - }) -} diff --git a/cli/cmd/volume/command.go b/cli/cmd/volume/command.go deleted file mode 100644 index 0aef21ce0..000000000 --- a/cli/cmd/volume/command.go +++ /dev/null @@ -1,158 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package volume - -import ( - "context" - "fmt" - - format "github.com/docker/compose-cli/cmd/formatter" - - "github.com/docker/compose-cli/aci" - "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/ecs" - "github.com/docker/compose-cli/pkg/progress" - - "github.com/hashicorp/go-multierror" - "github.com/spf13/cobra" -) - -// Command manage volumes -func Command(ctype string) *cobra.Command { - cmd := &cobra.Command{ - Use: "volume", - Short: "Manages volumes", - } - - cmd.AddCommand( - createVolume(ctype), - listVolume(), - rmVolume(), - inspectVolume(), - ) - return cmd -} - -func createVolume(ctype string) *cobra.Command { - var usage string - var short string - switch ctype { - case store.AciContextType: - usage = "create --storage-account ACCOUNT VOLUME" - short = "Creates an Azure file share to use as ACI volume." - case store.EcsContextType: - usage = "create [OPTIONS] VOLUME" - short = "Creates an EFS filesystem to use as AWS volume." - default: - usage = "create [OPTIONS] VOLUME" - short = "Creates a volume" - } - - var opts interface{} - cmd := &cobra.Command{ - Use: usage, - Short: short, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - c, err := client.New(ctx) - if err != nil { - return err - } - result, err := progress.RunWithStatus(ctx, func(ctx context.Context) (string, error) { - volume, err := c.VolumeService().Create(ctx, args[0], opts) - if err != nil { - return "", err - } - return volume.ID, nil - }) - if err != nil { - return err - } - fmt.Println(result) - return nil - }, - } - - switch ctype { - case store.AciContextType: - aciOpts := aci.VolumeCreateOptions{} - cmd.Flags().StringVar(&aciOpts.Account, "storage-account", "", "Storage account name") - _ = cmd.MarkFlagRequired("storage-account") - opts = &aciOpts - case store.EcsContextType: - ecsOpts := ecs.VolumeCreateOptions{} - cmd.Flags().StringVar(&ecsOpts.KmsKeyID, "kms-key", "", "ID of the AWS KMS CMK to be used to protect the encrypted file system") - cmd.Flags().StringVar(&ecsOpts.PerformanceMode, "performance-mode", "", "performance mode of the file system. (generalPurpose|maxIO)") - cmd.Flags().Float64Var(&ecsOpts.ProvisionedThroughputInMibps, "provisioned-throughput", 0, "throughput in MiB/s (1-1024)") - cmd.Flags().StringVar(&ecsOpts.ThroughputMode, "throughput-mode", "", "throughput mode (bursting|provisioned)") - opts = &ecsOpts - } - return cmd -} - -func rmVolume() *cobra.Command { - cmd := &cobra.Command{ - Use: "rm [OPTIONS] VOLUME [VOLUME...]", - Short: "Remove one or more volumes.", - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - c, err := client.New(cmd.Context()) - if err != nil { - return err - } - var errs *multierror.Error - for _, id := range args { - err = c.VolumeService().Delete(cmd.Context(), id, nil) - if err != nil { - errs = multierror.Append(errs, err) - continue - } - fmt.Println(id) - } - format.SetMultiErrorFormat(errs) - return errs.ErrorOrNil() - }, - } - return cmd -} - -func inspectVolume() *cobra.Command { - cmd := &cobra.Command{ - Use: "inspect VOLUME [VOLUME...]", - Short: "Inspect one or more volumes.", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - c, err := client.New(cmd.Context()) - if err != nil { - return err - } - v, err := c.VolumeService().Inspect(cmd.Context(), args[0]) - if err != nil { - return err - } - outJSON, err := format.ToStandardJSON(v) - if err != nil { - return err - } - fmt.Print(outJSON) - return nil - }, - } - return cmd -} diff --git a/cli/cmd/volume/list.go b/cli/cmd/volume/list.go deleted file mode 100644 index 38b54fdb2..000000000 --- a/cli/cmd/volume/list.go +++ /dev/null @@ -1,85 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package volume - -import ( - "fmt" - "io" - "os" - - formatter2 "github.com/docker/compose-cli/cmd/formatter" - - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/api/volumes" -) - -type listVolumeOpts struct { - format string - quiet bool -} - -func listVolume() *cobra.Command { - var opts listVolumeOpts - cmd := &cobra.Command{ - Use: "ls", - Short: "list available volumes in context.", - Args: cobra.ExactArgs(0), - RunE: func(cmd *cobra.Command, args []string) error { - c, err := client.New(cmd.Context()) - if err != nil { - return err - } - vols, err := c.VolumeService().List(cmd.Context()) - if err != nil { - return err - } - if opts.quiet { - for _, v := range vols { - fmt.Println(v.ID) - } - return nil - } - view := viewFromVolumeList(vols) - return formatter2.Print(view, opts.format, os.Stdout, func(w io.Writer) { - for _, vol := range view { - _, _ = fmt.Fprintf(w, "%s\t%s\n", vol.ID, vol.Description) - } - }, "ID", "DESCRIPTION") - }, - } - cmd.Flags().StringVar(&opts.format, "format", formatter2.PRETTY, "Format the output. Values: [pretty | json]. (Default: pretty)") - cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs") - return cmd -} - -type volumeView struct { - ID string - Description string -} - -func viewFromVolumeList(volumeList []volumes.Volume) []volumeView { - retList := make([]volumeView, len(volumeList)) - for i, v := range volumeList { - retList[i] = volumeView{ - ID: v.ID, - Description: v.Description, - } - } - return retList -} diff --git a/cli/config/flags.go b/cli/config/flags.go deleted file mode 100644 index f38d967e2..000000000 --- a/cli/config/flags.go +++ /dev/null @@ -1,81 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package config - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/pkg/errors" - "github.com/spf13/pflag" - - "github.com/docker/compose-cli/api/config" -) - -// ConfigFlags are the global CLI flags -// nolint stutter -type ConfigFlags struct { - Config string -} - -// AddConfigFlags adds persistent (global) flags -func (c *ConfigFlags) AddConfigFlags(flags *pflag.FlagSet) { - flags.StringVar(&c.Config, config.ConfigFlagName, confDir(), "Location of the client config files `DIRECTORY`") -} - -func confDir() string { - env := os.Getenv("DOCKER_CONFIG") - if env != "" { - return env - } - home, _ := os.UserHomeDir() - return filepath.Join(home, config.ConfigFileDir) -} - -// GetCurrentContext get current context based on opts, env vars -func GetCurrentContext(contextOpt string, configDir string, hosts []string) string { - // host and context flags cannot be both set at the same time -- the local backend enforces this when resolving hostname - // -H flag disables context --> set default as current - if len(hosts) > 0 { - return "default" - } - // DOCKER_HOST disables context --> set default as current - if _, present := os.LookupEnv("DOCKER_HOST"); present { - return "default" - } - res := contextOpt - if res == "" { - // check if DOCKER_CONTEXT env variable was set - if _, present := os.LookupEnv("DOCKER_CONTEXT"); present { - res = os.Getenv("DOCKER_CONTEXT") - } - - if res == "" { - config, err := config.LoadFile(configDir) - if err != nil { - fmt.Fprintln(os.Stderr, errors.Wrap(err, "WARNING")) - return "default" - } - res = config.CurrentContext - } - } - if res == "" { - res = "default" - } - return res -} diff --git a/cli/config/flags_test.go b/cli/config/flags_test.go deleted file mode 100644 index 3e6890215..000000000 --- a/cli/config/flags_test.go +++ /dev/null @@ -1,61 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package config - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" - - "gotest.tools/v3/assert" - - "github.com/docker/compose-cli/api/config" -) - -var contextSetConfig = []byte(`{ - "currentContext": "some-context" -}`) - -func TestDetermineCurrentContext(t *testing.T) { - d, err := ioutil.TempDir("", "") - // nolint errcheck - defer os.RemoveAll(d) - assert.NilError(t, err) - err = ioutil.WriteFile(filepath.Join(d, config.ConfigFileName), contextSetConfig, 0644) - assert.NilError(t, err) - - // If nothing set, fallback to default - c := GetCurrentContext("", "", []string{}) - assert.Equal(t, c, "default") - - // If context flag set, use that - c = GetCurrentContext("other-context", "", []string{}) - assert.Equal(t, c, "other-context") - - // If no context flag, use config - c = GetCurrentContext("", d, []string{}) - assert.Equal(t, c, "some-context") - - // Ensure context flag overrides config - c = GetCurrentContext("other-context", d, []string{}) - assert.Equal(t, "other-context", c) - - // Ensure host flag overrides context - c = GetCurrentContext("other-context", d, []string{"hostname"}) - assert.Equal(t, "default", c) -} diff --git a/cli/main.go b/cli/main.go deleted file mode 100644 index 2c0025946..000000000 --- a/cli/main.go +++ /dev/null @@ -1,370 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package main - -import ( - "context" - "fmt" - "math/rand" - "os" - "os/signal" - "path/filepath" - "regexp" - "strings" - "syscall" - "time" - - "github.com/compose-spec/compose-go/types" - "github.com/docker/cli/cli" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/api/backend" - "github.com/docker/compose-cli/api/config" - apicontext "github.com/docker/compose-cli/api/context" - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/cli/cmd" - contextcmd "github.com/docker/compose-cli/cli/cmd/context" - "github.com/docker/compose-cli/cli/cmd/login" - "github.com/docker/compose-cli/cli/cmd/logout" - "github.com/docker/compose-cli/cli/cmd/run" - "github.com/docker/compose-cli/cli/cmd/volume" - cliconfig "github.com/docker/compose-cli/cli/config" - "github.com/docker/compose-cli/cli/metrics" - "github.com/docker/compose-cli/cli/mobycli" - cliopts "github.com/docker/compose-cli/cli/options" - compose2 "github.com/docker/compose-cli/cmd/compose" - "github.com/docker/compose-cli/local" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/compose" - - // Backend registrations - _ "github.com/docker/compose-cli/aci" - _ "github.com/docker/compose-cli/ecs" - _ "github.com/docker/compose-cli/ecs/local" - _ "github.com/docker/compose-cli/local" -) - -var ( - contextAgnosticCommands = map[string]struct{}{ - "context": {}, - "login": {}, - "logout": {}, - "serve": {}, - "version": {}, - "backend-metadata": {}, - // Special hidden commands used by cobra for completion - "__complete": {}, - "__completeNoDesc": {}, - } - unknownCommandRegexp = regexp.MustCompile(`unknown docker command: "([^"]*)"`) -) - -func init() { - // initial hack to get the path of the project's bin dir - // into the env of this cli for development - path, err := filepath.Abs(filepath.Dir(os.Args[0])) - if err != nil { - fatal(errors.Wrap(err, "unable to get absolute bin path")) - } - - if err := os.Setenv("PATH", appendPaths(os.Getenv("PATH"), path)); err != nil { - panic(err) - } - // Seed random - rand.Seed(time.Now().UnixNano()) -} - -func appendPaths(envPath string, path string) string { - if envPath == "" { - return path - } - return strings.Join([]string{envPath, path}, string(os.PathListSeparator)) -} - -func isContextAgnosticCommand(cmd *cobra.Command) bool { - if cmd == nil { - return false - } - if _, ok := contextAgnosticCommands[cmd.Name()]; ok { - return true - } - return isContextAgnosticCommand(cmd.Parent()) -} - -func main() { - var opts cliopts.GlobalOpts - root := &cobra.Command{ - Use: "docker", - SilenceErrors: true, - SilenceUsage: true, - TraverseChildren: true, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - if !isContextAgnosticCommand(cmd) { - mobycli.ExecIfDefaultCtxType(cmd.Context(), cmd.Root()) - } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - return cmd.Help() - } - return fmt.Errorf("unknown docker command: %q", args[0]) - }, - } - - root.AddCommand( - contextcmd.Command(), - cmd.PsCommand(), - cmd.ServeCommand(), - cmd.ExecCommand(), - cmd.LogsCommand(), - cmd.RmCommand(), - cmd.StartCommand(), - cmd.InspectCommand(), - login.Command(), - logout.Command(), - cmd.VersionCommand(), - cmd.StopCommand(), - cmd.KillCommand(), - cmd.SecretCommand(), - cmd.PruneCommand(), - cmd.MetadataCommand(), - - // Place holders - cmd.EcsCommand(), - ) - - helpFunc := root.HelpFunc() - root.SetHelpFunc(func(cmd *cobra.Command, args []string) { - if !isContextAgnosticCommand(cmd) { - mobycli.ExecIfDefaultCtxType(cmd.Context(), cmd.Root()) - } - helpFunc(cmd, args) - }) - - flags := root.Flags() - opts.InstallFlags(flags) - opts.AddConfigFlags(flags) - flags.BoolVarP(&opts.Version, "version", "v", false, "Print version information and quit") - - flags.SetInterspersed(false) - - walk(root, func(c *cobra.Command) { - c.Flags().BoolP("help", "h", false, "Help for "+c.Name()) - }) - - // populate the opts with the global flags - flags.Parse(os.Args[1:]) // nolint: errcheck - - level, err := logrus.ParseLevel(opts.LogLevel) - if err != nil { - fmt.Fprintf(os.Stderr, "Unable to parse logging level: %s\n", opts.LogLevel) - os.Exit(1) - } - logrus.SetFormatter(&logrus.TextFormatter{ - DisableTimestamp: true, - DisableLevelTruncation: true, - }) - logrus.SetLevel(level) - if opts.Debug { - logrus.SetLevel(logrus.DebugLevel) - } - - ctx, cancel := newSigContext() - defer cancel() - - // --version should immediately be forwarded to the original cli - if opts.Version { - mobycli.Exec(root) - } - - if opts.Config == "" { - fatal(errors.New("config path cannot be empty")) - } - configDir := opts.Config - config.WithDir(configDir) - - currentContext := cliconfig.GetCurrentContext(opts.Context, configDir, opts.Hosts) - apicontext.WithCurrentContext(currentContext) - - s, err := store.New(configDir) - if err != nil { - mobycli.Exec(root) - } - store.WithContextStore(s) - - ctype := store.DefaultContextType - cc, _ := s.Get(currentContext) - if cc != nil { - ctype = cc.Type() - } - - service, err := getBackend(ctype, configDir, opts) - if err != nil { - fatal(err) - } - backend.WithBackend(service) - - root.AddCommand( - run.Command(ctype), - volume.Command(ctype), - ) - - // On default context, "compose" is implemented by CLI Plugin - proxy := api.NewServiceProxy().WithService(service.ComposeService()) - command := compose2.RootCommand(proxy) - - if ctype == store.AciContextType { - customizeCliForACI(command, proxy) - } - - root.AddCommand(command) - - if err = root.ExecuteContext(ctx); err != nil { - handleError(ctx, err, ctype, currentContext, cc, root) - } - metrics.Track(ctype, os.Args[1:], compose.SuccessStatus) -} - -func customizeCliForACI(command *cobra.Command, proxy *api.ServiceProxy) { - var domainName string - for _, c := range command.Commands() { - if c.Name() == "up" { - c.Flags().StringVar(&domainName, "domainname", "", "Container NIS domain name") - proxy.WithInterceptor(func(ctx context.Context, project *types.Project) { - if domainName != "" { - // arbitrarily set the domain name on the first service ; ACI backend will expose the entire project - project.Services[0].DomainName = domainName - } - - }) - } - } -} - -func getBackend(ctype string, configDir string, opts cliopts.GlobalOpts) (backend.Service, error) { - switch ctype { - case store.DefaultContextType, store.LocalContextType: - return local.GetLocalBackend(configDir, opts) - } - service, err := backend.Get(ctype) - if api.IsNotFoundError(err) { - return service, nil - } - return service, err -} - -func handleError(ctx context.Context, err error, ctype string, currentContext string, cc *store.DockerContext, root *cobra.Command) { - // if user canceled request, simply exit without any error message - if api.IsErrCanceled(err) || errors.Is(ctx.Err(), context.Canceled) { - metrics.Track(ctype, os.Args[1:], compose.CanceledStatus) - os.Exit(130) - } - if ctype == store.AwsContextType { - exit(currentContext, errors.Errorf(`%q context type has been renamed. Recreate the context by running: -$ docker context create %s `, cc.Type(), store.EcsContextType), ctype) - } - - // Context should always be handled by new CLI - requiredCmd, _, _ := root.Find(os.Args[1:]) - if requiredCmd != nil && isContextAgnosticCommand(requiredCmd) { - exit(currentContext, err, ctype) - } - mobycli.ExecIfDefaultCtxType(ctx, root) - - checkIfUnknownCommandExistInDefaultContext(err, currentContext, ctype) - - exit(currentContext, err, ctype) -} - -func exit(ctx string, err error, ctype string) { - if exit, ok := err.(cli.StatusError); ok { - metrics.Track(ctype, os.Args[1:], compose.SuccessStatus) - os.Exit(exit.StatusCode) - } - - var composeErr compose.Error - metricsStatus := compose.FailureStatus - exitCode := 1 - if errors.As(err, &composeErr) { - metricsStatus = composeErr.GetMetricsFailureCategory().MetricsStatus - exitCode = composeErr.GetMetricsFailureCategory().ExitCode - } - if strings.HasPrefix(err.Error(), "unknown shorthand flag:") || strings.HasPrefix(err.Error(), "unknown flag:") || strings.HasPrefix(err.Error(), "unknown docker command:") { - metricsStatus = compose.CommandSyntaxFailure.MetricsStatus - exitCode = compose.CommandSyntaxFailure.ExitCode - } - metrics.Track(ctype, os.Args[1:], metricsStatus) - - if errors.Is(err, api.ErrLoginRequired) { - fmt.Fprintln(os.Stderr, err) - os.Exit(api.ExitCodeLoginRequired) - } - - if compose2.Warning != "" { - logrus.Warn(err) - fmt.Fprintln(os.Stderr, compose2.Warning) - } - - if errors.Is(err, api.ErrNotImplemented) { - name := metrics.GetCommand(os.Args[1:]) - fmt.Fprintf(os.Stderr, "Command %q not available in current context (%s)\n", name, ctx) - - os.Exit(1) - } - - fmt.Fprintln(os.Stderr, err) - os.Exit(exitCode) -} - -func fatal(err error) { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) -} - -func checkIfUnknownCommandExistInDefaultContext(err error, currentContext string, contextType string) { - submatch := unknownCommandRegexp.FindSubmatch([]byte(err.Error())) - if len(submatch) == 2 { - dockerCommand := string(submatch[1]) - - if mobycli.IsDefaultContextCommand(dockerCommand) { - fmt.Fprintf(os.Stderr, "Command %q not available in current context (%s), you can use the \"default\" context to run this command\n", dockerCommand, currentContext) - metrics.Track(contextType, os.Args[1:], compose.FailureStatus) - os.Exit(1) - } - } -} - -func newSigContext() (context.Context, func()) { - ctx, cancel := context.WithCancel(context.Background()) - s := make(chan os.Signal, 1) - signal.Notify(s, syscall.SIGTERM, syscall.SIGINT) - go func() { - <-s - cancel() - }() - return ctx, cancel -} - -func walk(c *cobra.Command, f func(*cobra.Command)) { - f(c) - for _, c := range c.Commands() { - walk(c, f) - } -} diff --git a/cli/main_test.go b/cli/main_test.go deleted file mode 100644 index 85caf2034..000000000 --- a/cli/main_test.go +++ /dev/null @@ -1,44 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package main - -import ( - "os" - "testing" - - "gotest.tools/v3/assert" - - "github.com/docker/compose-cli/cli/cmd" - "github.com/docker/compose-cli/cli/cmd/context" - "github.com/docker/compose-cli/cli/cmd/login" - "github.com/docker/compose-cli/cli/cmd/run" -) - -func TestCheckOwnCommand(t *testing.T) { - assert.Assert(t, isContextAgnosticCommand(login.Command())) - assert.Assert(t, isContextAgnosticCommand(context.Command())) - assert.Assert(t, isContextAgnosticCommand(cmd.ServeCommand())) - assert.Assert(t, !isContextAgnosticCommand(run.Command("default"))) - assert.Assert(t, !isContextAgnosticCommand(cmd.ExecCommand())) - assert.Assert(t, !isContextAgnosticCommand(cmd.LogsCommand())) - assert.Assert(t, !isContextAgnosticCommand(cmd.PsCommand())) -} - -func TestAppendPaths(t *testing.T) { - assert.Equal(t, appendPaths("", "/bin/path"), "/bin/path") - assert.Equal(t, appendPaths("path1", "binaryPath"), "path1"+string(os.PathListSeparator)+"binaryPath") -} diff --git a/cli/metrics/client.go b/cli/metrics/client.go deleted file mode 100644 index 570a135eb..000000000 --- a/cli/metrics/client.go +++ /dev/null @@ -1,91 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package metrics - -import ( - "bytes" - "context" - "encoding/json" - "net" - "net/http" - "os" - "time" -) - -type client struct { - httpClient *http.Client -} - -// Command is a command -type Command struct { - Command string `json:"command"` - Context string `json:"context"` - Source string `json:"source"` - Status string `json:"status"` -} - -// CLISource is sent for cli metrics -var CLISource = "cli" - -func init() { - if v, ok := os.LookupEnv("DOCKER_METRICS_SOURCE"); ok { - CLISource = v - } -} - -// Client sends metrics to Docker Desktopn -type Client interface { - // Send sends the command to Docker Desktop. Note that the function doesn't - // return anything, not even an error, this is because we don't really care - // if the metrics were sent or not. We only fire and forget. - Send(Command) -} - -// NewClient returns a new metrics client -func NewClient() Client { - return &client{ - httpClient: &http.Client{ - Transport: &http.Transport{ - DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { - return conn() - }, - }, - }, - } -} - -func (c *client) Send(command Command) { - result := make(chan bool, 1) - go func() { - postMetrics(command, c) - result <- true - }() - - // wait for the post finished, or timeout in case anything freezes. - // Posting metrics without Desktop listening returns in less than a ms, and a handful of ms (often <2ms) when Desktop is listening - select { - case <-result: - case <-time.After(50 * time.Millisecond): - } -} - -func postMetrics(command Command, c *client) { - req, err := json.Marshal(command) - if err == nil { - _, _ = c.httpClient.Post("http://localhost/usage", "application/json", bytes.NewBuffer(req)) - } -} diff --git a/cli/metrics/commands.go b/cli/metrics/commands.go deleted file mode 100644 index e021897ff..000000000 --- a/cli/metrics/commands.go +++ /dev/null @@ -1,149 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package metrics - -var commandFlags = []string{ - //added to catch scan details - "--version", "--login", -} - -// Generated with generatecommands/main.go -var managementCommands = []string{ - "help", - "ecs", - "scan", - "app", - "builder", - "imagetools", - "buildx", - "checkpoint", - "config", - "container", - "context", - "create", - "image", - "manifest", - "network", - "node", - "plugin", - "secret", - "service", - "stack", - "swarm", - "system", - "key", - "signer", - "trust", - "volume", - "login", - "logout", - "compose", -} - -var commands = []string{ - "bundle", - "completion", - "init", - "inspect", - "install", - "deploy", - "list", - "ls", - "cp", - "merge", - "pull", - "push", - "render", - "split", - "status", - "uninstall", - "upgrade", - "validate", - "version", - "build", - "prune", - "create", - "bake", - "f", - "b", - "du", - "rm", - "stop", - "use", - "remove", - "attach", - "commit", - "cp", - "diff", - "exec", - "export", - "kill", - "logs", - "ps", - "pause", - "port", - "rename", - "restart", - "run", - "start", - "stats", - "top", - "unpause", - "update", - "wait", - "aci", - "ecs", - "show", - "history", - "import", - "load", - "images", - "rmi", - "save", - "tag", - "annotate", - "connect", - "disconnect", - "demote", - "promote", - "disable", - "enable", - "set", - "rollback", - "scale", - "up", - "down", - "services", - "ca", - "join", - "join-token", - "leave", - "unlock", - "unlock-key", - "df", - "events", - "info", - "generate", - "add", - "revoke", - "sign", - "login", - "azure", - "logout", - "search", - "convert", -} diff --git a/cli/metrics/conn_e2e.go b/cli/metrics/conn_e2e.go deleted file mode 100644 index 53e87c9e2..000000000 --- a/cli/metrics/conn_e2e.go +++ /dev/null @@ -1,30 +0,0 @@ -// +build e2e - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package metrics - -import ( - "os" -) - -func init() { - testSocket, defined := os.LookupEnv("TEST_METRICS_SOCKET") - if defined { - socket = testSocket - } -} diff --git a/cli/metrics/conn_other.go b/cli/metrics/conn_other.go deleted file mode 100644 index d51945f6a..000000000 --- a/cli/metrics/conn_other.go +++ /dev/null @@ -1,29 +0,0 @@ -// +build !windows - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package metrics - -import "net" - -var ( - socket = "/var/run/docker-cli.sock" -) - -func conn() (net.Conn, error) { - return net.Dial("unix", socket) -} diff --git a/cli/metrics/conn_windows.go b/cli/metrics/conn_windows.go deleted file mode 100644 index f10a5472c..000000000 --- a/cli/metrics/conn_windows.go +++ /dev/null @@ -1,39 +0,0 @@ -// +build windows - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package metrics - -import ( - "net" - "strings" - "time" - - "github.com/Microsoft/go-winio" -) - -var ( - socket = `\\.\pipe\docker_cli` -) - -func conn() (net.Conn, error) { - if strings.HasPrefix(socket, `\\.\pipe\`) { - timeout := 200 * time.Millisecond - return winio.DialPipe(socket, &timeout) - } - return net.Dial("unix", socket) -} diff --git a/cli/metrics/generatecommands/main.go b/cli/metrics/generatecommands/main.go deleted file mode 100644 index caa291973..000000000 --- a/cli/metrics/generatecommands/main.go +++ /dev/null @@ -1,107 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package main - -import ( - "fmt" - "os/exec" - "strings" - - "github.com/docker/compose-cli/pkg/utils" -) - -var managementCommands = []string{"ecs", "scan"} - -var commands = []string{} - -func main() { - fmt.Println("Walking through docker help to list commands...") - getCommands() - getCommands("compose") - - fmt.Printf(` -var managementCommands = []string{ - "help", - "%s", -} - -var commands = []string{ - "%s", -} -`, strings.Join(managementCommands, "\", \n\t\""), strings.Join(commands, "\", \n\t\"")) -} - -const ( - mgtCommandsSection = "Management Commands:" - commandsSection = "Commands:" - aliasesSection = "Aliases:" -) - -func getCommands(execCommands ...string) { - withHelp := append(execCommands, "--help") - cmd := exec.Command("docker", withHelp...) - output, err := cmd.Output() - if err != nil { - return - } - text := string(output) - lines := strings.Split(text, "\n") - section := "" - for _, line := range lines { - trimmedLine := strings.TrimSpace(line) - if strings.HasPrefix(trimmedLine, mgtCommandsSection) { - section = mgtCommandsSection - continue - } - if strings.HasPrefix(trimmedLine, commandsSection) || strings.HasPrefix(trimmedLine, "Available Commands:") { - section = commandsSection - if len(execCommands) > 0 { - command := execCommands[len(execCommands)-1] - managementCommands = append(managementCommands, command) - } - continue - } - if strings.HasPrefix(trimmedLine, aliasesSection) { - section = aliasesSection - continue - } - if trimmedLine == "" { - section = "" - continue - } - - tokens := strings.Split(trimmedLine, " ") - command := strings.Replace(tokens[0], "*", "", 1) - switch section { - case mgtCommandsSection: - getCommands(append(execCommands, command)...) - case commandsSection: - if !utils.StringContains(commands, command) { - commands = append(commands, command) - } - getCommands(append(execCommands, command)...) - case aliasesSection: - aliases := strings.Split(trimmedLine, ",") - for _, alias := range aliases { - trimmedAlias := strings.TrimSpace(alias) - if !utils.StringContains(commands, trimmedAlias) { - commands = append(commands, trimmedAlias) - } - } - } - } -} diff --git a/cli/metrics/metrics.go b/cli/metrics/metrics.go deleted file mode 100644 index fcd3967ac..000000000 --- a/cli/metrics/metrics.go +++ /dev/null @@ -1,91 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package metrics - -import ( - "os" - "strings" - - "github.com/docker/compose-cli/pkg/utils" -) - -// Track sends the tracking analytics to Docker Desktop -func Track(context string, args []string, status string) { - if isInvokedAsCliBackend() { - return - } - command := GetCommand(args) - if command != "" { - c := NewClient() - c.Send(Command{ - Command: command, - Context: context, - Source: CLISource, - Status: status, - }) - } -} - -func isInvokedAsCliBackend() bool { - executable := os.Args[0] - return strings.HasSuffix(executable, "-backend") -} - -func isCommand(word string) bool { - return utils.StringContains(commands, word) || isManagementCommand(word) -} - -func isManagementCommand(word string) bool { - return utils.StringContains(managementCommands, word) -} - -func isCommandFlag(word string) bool { - return utils.StringContains(commandFlags, word) -} - -// HasQuietFlag returns true if one of the arguments is `--quiet` or `-q` -func HasQuietFlag(args []string) bool { - for _, a := range args { - switch a { - case "--quiet", "-q": - return true - } - } - return false -} - -// GetCommand get the invoked command -func GetCommand(args []string) string { - result := "" - onlyFlags := false - for _, arg := range args { - if arg == "--help" { - result = strings.TrimSpace(arg + " " + result) - continue - } - if arg == "--" { - break - } - if isCommandFlag(arg) || (!onlyFlags && isCommand(arg)) { - result = strings.TrimSpace(result + " " + arg) - if isCommand(arg) && !isManagementCommand(arg) { - onlyFlags = true - } - } - } - return result -} diff --git a/cli/metrics/metrics_test.go b/cli/metrics/metrics_test.go deleted file mode 100644 index 8711a07b8..000000000 --- a/cli/metrics/metrics_test.go +++ /dev/null @@ -1,337 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package metrics - -import ( - "testing" - - "gotest.tools/v3/assert" -) - -func TestHasQuietFlag(t *testing.T) { - cases := []struct { - name string - args []string - expected bool - }{ - { - name: "long flag", - args: []string{"build", "-t", "tag", "--quiet", "."}, - expected: true, - }, - { - name: "short flag", - args: []string{"build", "-t", "tag", "-q", "."}, - expected: true, - }, - { - name: "no flag", - args: []string{"build", "-t", "tag", "."}, - expected: false, - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - result := HasQuietFlag(c.args) - assert.Equal(t, c.expected, result) - }) - } -} - -func TestGetCommand(t *testing.T) { - testCases := []struct { - name string - args []string - expected string - }{ - { - name: "with long flags", - args: []string{"--debug", "run"}, - expected: "run", - }, - { - name: "with short flags", - args: []string{"-D", "run"}, - expected: "run", - }, - { - name: "with flags with value", - args: []string{"--debug", "--str", "str-value", "run"}, - expected: "run", - }, - { - name: "with --", - args: []string{"--debug", "--str", "str-value", "--", "run"}, - expected: "", - }, - { - name: "without a command", - args: []string{"--debug", "--str", "str-value"}, - expected: "", - }, - { - name: "management command", - args: []string{"image", "ls"}, - expected: "image ls", - }, - { - name: "management command with flag", - args: []string{"image", "--test", "ls"}, - expected: "image ls", - }, - { - name: "management subcommand with flag", - args: []string{"image", "ls", "-q"}, - expected: "image ls", - }, - { - name: "azure login", - args: []string{"login", "azure"}, - expected: "login azure", - }, - { - name: "azure logout", - args: []string{"logout", "azure"}, - expected: "logout azure", - }, - { - name: "azure login with flags", - args: []string{"login", "-u", "test", "azure"}, - expected: "login azure", - }, - { - name: "login to a registry", - args: []string{"login", "myregistry"}, - expected: "login", - }, - { - name: "logout from a registry", - args: []string{"logout", "myregistry"}, - expected: "logout", - }, - { - name: "context create aci", - args: []string{"context", "create", "aci"}, - expected: "context create aci", - }, - { - name: "context create ecs", - args: []string{"context", "create", "ecs"}, - expected: "context create ecs", - }, - { - name: "create a context from another context", - args: []string{"context", "create", "test-context", "--from=default"}, - expected: "context create", - }, - { - name: "create a container", - args: []string{"create"}, - expected: "create", - }, - { - name: "start a container named aci", - args: []string{"start", "aci"}, - expected: "start", - }, - { - name: "create a container named test-container", - args: []string{"create", "test-container"}, - expected: "create", - }, - { - name: "create with flags", - args: []string{"create", "--rm", "test"}, - expected: "create", - }, - { - name: "compose up -f xxx", - args: []string{"compose", "-f", "up", "titi.yaml"}, - expected: "compose up", - }, - { - name: "compose -f xxx up", - args: []string{"compose", "-f", "titi.yaml", "up"}, - expected: "compose up", - }, - { - name: "-D compose -f xxx up", - args: []string{"--debug", "compose", "-f", "titi.yaml", "up"}, - expected: "compose up", - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - result := GetCommand(testCase.args) - assert.Equal(t, testCase.expected, result) - }) - } -} - -func TestKeepHelpCommands(t *testing.T) { - testCases := []struct { - name string - args []string - expected string - }{ - { - name: "run with help flag", - args: []string{"run", "--help"}, - expected: "--help run", - }, - { - name: "with help flag before-after commands", - args: []string{"compose", "--help", "up"}, - expected: "--help compose up", - }, - { - name: "help flag", - args: []string{"--help"}, - expected: "--help", - }, - { - name: "help commands", - args: []string{"help", "run"}, - expected: "help run", - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - result := GetCommand(testCase.args) - assert.Equal(t, testCase.expected, result) - }) - } -} - -func TestEcs(t *testing.T) { - testCases := []struct { - name string - args []string - expected string - }{ - { - name: "compose up", - args: []string{"ecs", "compose", "-f", "test", "up"}, - expected: "ecs compose up", - }, - { - name: "compose up", - args: []string{"ecs", "compose", "--file", "test", "up"}, - expected: "ecs compose up", - }, - { - name: "compose up", - args: []string{"ecs", "compose", "--file", "test", "-n", "test", "up"}, - expected: "ecs compose up", - }, - { - name: "compose up", - args: []string{"ecs", "compose", "--file", "test", "--project-name", "test", "up"}, - expected: "ecs compose up", - }, - { - name: "compose up", - args: []string{"ecs", "compose", "up"}, - expected: "ecs compose up", - }, - { - name: "compose down", - args: []string{"ecs", "compose", "-f", "test", "down"}, - expected: "ecs compose down", - }, - { - name: "compose down", - args: []string{"ecs", "compose", "down"}, - expected: "ecs compose down", - }, - { - name: "compose ps", - args: []string{"ecs", "compose", "-f", "test", "ps"}, - expected: "ecs compose ps", - }, - { - name: "compose ps", - args: []string{"ecs", "compose", "ps"}, - expected: "ecs compose ps", - }, - { - name: "compose logs", - args: []string{"ecs", "compose", "-f", "test", "logs"}, - expected: "ecs compose logs", - }, - { - name: "ecs", - args: []string{"ecs", "anything"}, - expected: "ecs", - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - result := GetCommand(testCase.args) - assert.Equal(t, testCase.expected, result) - }) - } -} - -func TestScan(t *testing.T) { - testCases := []struct { - name string - args []string - expected string - }{ - { - name: "scan", - args: []string{"scan"}, - expected: "scan", - }, - { - name: "scan image with long flags", - args: []string{"scan", "--file", "file", "myimage"}, - expected: "scan", - }, - { - name: "scan image with short flags", - args: []string{"scan", "-f", "file", "myimage"}, - expected: "scan", - }, - { - name: "scan with long flag", - args: []string{"scan", "--dependency-tree", "myimage"}, - expected: "scan", - }, - { - name: "auth", - args: []string{"scan", "--login"}, - expected: "scan --login", - }, - { - name: "version", - args: []string{"scan", "--version"}, - expected: "scan --version", - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - result := GetCommand(testCase.args) - assert.Equal(t, testCase.expected, result) - }) - } -} diff --git a/cli/mobycli/exec.go b/cli/mobycli/exec.go deleted file mode 100644 index 552987f85..000000000 --- a/cli/mobycli/exec.go +++ /dev/null @@ -1,168 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package mobycli - -import ( - "context" - "fmt" - "os" - "os/exec" - "os/signal" - "path/filepath" - "regexp" - - apicontext "github.com/docker/compose-cli/api/context" - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/cli/metrics" - "github.com/docker/compose-cli/cli/mobycli/resolvepath" - "github.com/spf13/cobra" - - "github.com/docker/compose-cli/pkg/compose" - "github.com/docker/compose-cli/pkg/utils" -) - -var delegatedContextTypes = []string{store.DefaultContextType} - -// ComDockerCli name of the classic cli binary -const ComDockerCli = "com.docker.cli" - -// ExecIfDefaultCtxType delegates to com.docker.cli if on moby context -func ExecIfDefaultCtxType(ctx context.Context, root *cobra.Command) { - currentContext := apicontext.Current() - - s := store.Instance() - - currentCtx, err := s.Get(currentContext) - // Only run original docker command if the current context is not ours. - if err != nil || mustDelegateToMoby(currentCtx.Type()) { - Exec(root) - } -} - -func mustDelegateToMoby(ctxType string) bool { - for _, ctype := range delegatedContextTypes { - if ctxType == ctype { - return true - } - } - return false -} - -// Exec delegates to com.docker.cli if on moby context -func Exec(root *cobra.Command) { - childExit := make(chan bool) - err := RunDocker(childExit, os.Args[1:]...) - childExit <- true - if err != nil { - if exiterr, ok := err.(*exec.ExitError); ok { - exitCode := exiterr.ExitCode() - metrics.Track(store.DefaultContextType, os.Args[1:], compose.ByExitCode(exitCode).MetricsStatus) - os.Exit(exitCode) - } - metrics.Track(store.DefaultContextType, os.Args[1:], compose.FailureStatus) - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - commandArgs := os.Args[1:] - command := metrics.GetCommand(commandArgs) - if command == "build" && !metrics.HasQuietFlag(commandArgs) { - utils.DisplayScanSuggestMsg() - } - if command == "login" && !metrics.HasQuietFlag(commandArgs) { - displayPATSuggestMsg(commandArgs) - } - metrics.Track(store.DefaultContextType, os.Args[1:], compose.SuccessStatus) - - os.Exit(0) -} - -// RunDocker runs a docker command, and forward signals to the shellout command (stops listening to signals when an event is sent to childExit) -func RunDocker(childExit chan bool, args ...string) error { - execBinary, err := resolvepath.LookPath(ComDockerCli) - if err != nil { - execBinary = findBinary(ComDockerCli) - if execBinary == "" { - fmt.Fprintln(os.Stderr, err) - fmt.Fprintln(os.Stderr, "Current PATH : "+os.Getenv("PATH")) - os.Exit(1) - } - } - cmd := exec.Command(execBinary, args...) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - signals := make(chan os.Signal, 1) - signal.Notify(signals) // catch all signals - go func() { - for { - select { - case sig := <-signals: - if cmd.Process == nil { - continue // can happen if receiving signal before the process is actually started - } - // In go1.14+, the go runtime issues SIGURG as an interrupt to - // support preemptable system calls on Linux. Since we can't - // forward that along we'll check that here. - if isRuntimeSig(sig) { - continue - } - _ = cmd.Process.Signal(sig) - case <-childExit: - return - } - } - }() - - return cmd.Run() -} - -func findBinary(filename string) string { - currentBinaryPath, err := os.Executable() - if err != nil { - return "" - } - currentBinaryPath, err = filepath.EvalSymlinks(currentBinaryPath) - if err != nil { - return "" - } - binaryPath := filepath.Join(filepath.Dir(currentBinaryPath), filename) - if _, err := os.Stat(binaryPath); err != nil { - return "" - } - return binaryPath -} - -// IsDefaultContextCommand checks if the command exists in the classic cli (issues a shellout --help) -func IsDefaultContextCommand(dockerCommand string) bool { - cmd := exec.Command(ComDockerCli, dockerCommand, "--help") - b, e := cmd.CombinedOutput() - if e != nil { - fmt.Println(e) - } - return regexp.MustCompile("Usage:\\s*docker\\s*" + dockerCommand).Match(b) -} - -// ExecSilent executes a command and do redirect output to stdOut, return output -func ExecSilent(ctx context.Context, args ...string) ([]byte, error) { - if len(args) == 0 { - args = os.Args[1:] - } - cmd := exec.CommandContext(ctx, ComDockerCli, args...) - cmd.Stderr = os.Stderr - return cmd.Output() -} diff --git a/cli/mobycli/exec_test.go b/cli/mobycli/exec_test.go deleted file mode 100644 index 7138464f8..000000000 --- a/cli/mobycli/exec_test.go +++ /dev/null @@ -1,46 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package mobycli - -import ( - "testing" - - "gotest.tools/v3/assert" - - "github.com/docker/compose-cli/api/context/store" -) - -func TestDelegateContextTypeToMoby(t *testing.T) { - - isDelegated := func(val string) bool { - for _, ctx := range delegatedContextTypes { - if ctx == val { - return true - } - } - return false - } - - allCtx := []string{store.AciContextType, store.EcsContextType, store.AwsContextType, store.DefaultContextType} - for _, ctx := range allCtx { - if isDelegated(ctx) { - assert.Assert(t, mustDelegateToMoby(ctx)) - continue - } - assert.Assert(t, !mustDelegateToMoby(ctx)) - } -} diff --git a/cli/mobycli/exec_unix.go b/cli/mobycli/exec_unix.go deleted file mode 100644 index 5d3f447b6..000000000 --- a/cli/mobycli/exec_unix.go +++ /dev/null @@ -1,29 +0,0 @@ -// +build !windows - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package mobycli - -import ( - "os" - - "golang.org/x/sys/unix" -) - -func isRuntimeSig(s os.Signal) bool { - return s == unix.SIGURG -} diff --git a/cli/mobycli/exec_windows.go b/cli/mobycli/exec_windows.go deleted file mode 100644 index 9111ec579..000000000 --- a/cli/mobycli/exec_windows.go +++ /dev/null @@ -1,23 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package mobycli - -import "os" - -func isRuntimeSig(s os.Signal) bool { - return false -} diff --git a/cli/mobycli/job_windows.go b/cli/mobycli/job_windows.go deleted file mode 100644 index f930e18f8..000000000 --- a/cli/mobycli/job_windows.go +++ /dev/null @@ -1,56 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package mobycli - -import ( - "fmt" - "unsafe" - - "golang.org/x/sys/windows" -) - -func init() { - if err := killSubProcessesOnClose(); err != nil { - fmt.Println("failed to create job:", err) - } -} - -// killSubProcessesOnClose will ensure on windows that all child processes of the current process are killed if parent is killed. -func killSubProcessesOnClose() error { - job, err := windows.CreateJobObject(nil, nil) - if err != nil { - return err - } - info := windows.JOBOBJECT_EXTENDED_LIMIT_INFORMATION{ - BasicLimitInformation: windows.JOBOBJECT_BASIC_LIMIT_INFORMATION{ - LimitFlags: windows.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, - }, - } - if _, err := windows.SetInformationJobObject( - job, - windows.JobObjectExtendedLimitInformation, - uintptr(unsafe.Pointer(&info)), - uint32(unsafe.Sizeof(info))); err != nil { - _ = windows.CloseHandle(job) - return err - } - if err := windows.AssignProcessToJobObject(job, windows.CurrentProcess()); err != nil { - _ = windows.CloseHandle(job) - return err - } - return nil -} diff --git a/cli/mobycli/pat_suggest.go b/cli/mobycli/pat_suggest.go deleted file mode 100644 index 2ba157e25..000000000 --- a/cli/mobycli/pat_suggest.go +++ /dev/null @@ -1,78 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package mobycli - -import ( - "fmt" - "os" - "strings" - - "github.com/docker/cli/cli/config" - "github.com/docker/docker/registry" - "github.com/hashicorp/go-uuid" -) - -const ( - // patSuggestMsg is a message to suggest the use of PAT (personal access tokens). - patSuggestMsg = `Logging in with your password grants your terminal complete access to your account. -For better security, log in with a limited-privilege personal access token. Learn more at https://docs.docker.com/go/access-tokens/` - - // patPrefix represents a docker personal access token prefix. - patPrefix = "dckrp_" -) - -// displayPATSuggestMsg displays a message suggesting users to use PATs instead of passwords to reduce scope. -func displayPATSuggestMsg(cmdArgs []string) { - if os.Getenv("DOCKER_PAT_SUGGEST") == "false" { - return - } - if !isUsingDefaultRegistry(cmdArgs) { - return - } - authCfg, err := config.LoadDefaultConfigFile(os.Stderr).GetAuthConfig(registry.IndexServer) - if err != nil { - return - } - if !isUsingPassword(authCfg.Password) { - return - } - fmt.Fprintf(os.Stderr, "\n"+patSuggestMsg+"\n") -} - -func isUsingDefaultRegistry(cmdArgs []string) bool { - for i := 1; i < len(cmdArgs); i++ { - if strings.HasPrefix(cmdArgs[i], "-") { - i++ - continue - } - return cmdArgs[i] == registry.IndexServer - } - return true -} - -func isUsingPassword(pass string) bool { - if pass == "" { // ignore if no password (or SSO) - return false - } - if _, err := uuid.ParseUUID(pass); err == nil { - return false - } - if strings.HasPrefix(pass, patPrefix) { - return false - } - return true -} diff --git a/cli/mobycli/pat_suggest_test.go b/cli/mobycli/pat_suggest_test.go deleted file mode 100644 index 9fe4fff49..000000000 --- a/cli/mobycli/pat_suggest_test.go +++ /dev/null @@ -1,99 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package mobycli - -import ( - "testing" - - "github.com/docker/docker/registry" - "gotest.tools/assert" -) - -func TestIsUsingDefaultRegistry(t *testing.T) { - testCases := []struct { - name string - input []string - expected bool - }{ - { - "without flags", - []string{"login"}, - true, - }, - { - "login with flags", - []string{"login", "-u", "test", "-p", "testpass"}, - true, - }, - { - "login to default registry", - []string{"login", registry.IndexServer}, - true, - }, - { - "login to different registry", - []string{"login", "registry.example.com"}, - false, - }, - { - "login with flags to different registry", - []string{"login", "-u", "test", "-p", "testpass", "registry.example.com"}, - false, - }, - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - result := isUsingDefaultRegistry(testCase.input) - assert.Equal(t, testCase.expected, result) - }) - } -} - -func TestIsUsingPassword(t *testing.T) { - testCases := []struct { - name string - input string - expected bool - }{ - { - "regular password", - "mypass", - true, - }, - { - "no password or sso", - "", - false, - }, - { - "personal access token", - "1508b8bd-b80c-452d-9a7a-ee5607c41bcd", - false, - }, - { - "prefixed personal access token", - "dckrp_ee5607c41bcd", - false, - }, - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - result := isUsingPassword(testCase.input) - assert.Equal(t, testCase.expected, result) - }) - } -} diff --git a/cli/mobycli/resolvepath/lp_unix.go b/cli/mobycli/resolvepath/lp_unix.go deleted file mode 100644 index 44caa3648..000000000 --- a/cli/mobycli/resolvepath/lp_unix.go +++ /dev/null @@ -1,28 +0,0 @@ -// +build !windows - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package resolvepath - -import ( - "os/exec" -) - -// LookPath simply delegate to os/exec.LookPath if not on windows -func LookPath(file string) (string, error) { - return exec.LookPath(file) -} diff --git a/cli/mobycli/resolvepath/lp_windows.go b/cli/mobycli/resolvepath/lp_windows.go deleted file mode 100644 index 65d9b5b89..000000000 --- a/cli/mobycli/resolvepath/lp_windows.go +++ /dev/null @@ -1,126 +0,0 @@ -/* -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -* Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. -* Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -package resolvepath - -import ( - "errors" - "os" - "os/exec" - "path/filepath" - "strings" -) - -// ErrNotFound is the error resulting if a path search failed to find an executable file. -var ErrNotFound = errors.New("executable file not found in %PATH%") - -func chkStat(file string) error { - d, err := os.Stat(file) - if err != nil { - return err - } - if d.IsDir() { - return os.ErrPermission - } - return nil -} - -func hasExt(file string) bool { - i := strings.LastIndex(file, ".") - if i < 0 { - return false - } - return strings.LastIndexAny(file, `:\/`) < i -} - -func findExecutable(file string, exts []string) (string, error) { - if len(exts) == 0 { - return file, chkStat(file) - } - if hasExt(file) { - if chkStat(file) == nil { - return file, nil - } - } - for _, e := range exts { - if f := file + e; chkStat(f) == nil { - return f, nil - } - } - return "", os.ErrNotExist -} - -// LookPath searches for an executable named file in the -// directories named by the PATH environment variable. -// If file contains a slash, it is tried directly and the PATH is not consulted. -// LookPath also uses PATHEXT environment variable to match -// a suitable candidate. -// The result may be an absolute path or a path relative to the current directory. -func LookPath(file string) (string, error) { - var exts []string - x := os.Getenv(`PATHEXT`) - if x != "" { - for _, e := range strings.Split(strings.ToLower(x), `;`) { - if e == "" { - continue - } - if e[0] != '.' { - e = "." + e - } - exts = append(exts, e) - } - } else { - exts = []string{".com", ".exe", ".bat", ".cmd"} - } - - if strings.ContainsAny(file, `:\/`) { - if f, err := findExecutable(file, exts); err == nil { - return f, nil - } else { - return "", &exec.Error{file, err} - } - } - // See https://github.com/golang/go/issues/38736 - // DO NOT lookup current folder - //if f, err := findExecutable(filepath.Join(".", file), exts); err == nil { - // return f, nil - //} - path := os.Getenv("path") - for _, dir := range filepath.SplitList(path) { - // empty dir means dupicate semicolon in PATH, should not resolve files in current working dir... - if strings.TrimSpace(dir) == "" { - continue - } - if f, err := findExecutable(filepath.Join(dir, file), exts); err == nil { - return f, nil - } - } - return "", &exec.Error{file, ErrNotFound} -} diff --git a/cli/options/options.go b/cli/options/options.go deleted file mode 100644 index c65e62aed..000000000 --- a/cli/options/options.go +++ /dev/null @@ -1,30 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package options - -import ( - cliconfig "github.com/docker/compose-cli/cli/config" - - cliflags "github.com/docker/cli/cli/flags" -) - -// GlobalOpts contains the global CLI options -type GlobalOpts struct { - cliconfig.ConfigFlags - cliflags.CommonOptions - Version bool -} diff --git a/cli/options/run/opts.go b/cli/options/run/opts.go deleted file mode 100644 index 3388d673f..000000000 --- a/cli/options/run/opts.go +++ /dev/null @@ -1,203 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package run - -import ( - "fmt" - "strconv" - "strings" - "time" - - "github.com/docker/compose-cli/utils" - - "github.com/compose-spec/compose-go/types" - "github.com/containerd/containerd/platforms" - "github.com/docker/cli/opts" - "github.com/docker/docker/pkg/namesgenerator" - "github.com/docker/go-connections/nat" - specs "github.com/opencontainers/image-spec/specs-go/v1" - - "github.com/docker/compose-cli/api/containers" -) - -// Opts contain run command options -type Opts struct { - Name string - Command []string - Publish []string - Labels []string - Volumes []string - Cpus float64 - Memory utils.MemBytes - Detach bool - Environment []string - EnvironmentFiles []string - RestartPolicyCondition string - DomainName string - Rm bool - HealthCmd string - HealthInterval time.Duration - HealthRetries int - HealthStartPeriod time.Duration - HealthTimeout time.Duration - Platform string -} - -// RestartPolicyList all available restart policy values -var RestartPolicyList = []string{containers.RestartPolicyRunNo, containers.RestartPolicyRunAlways, containers.RestartPolicyOnFailure} - -// ToContainerConfig convert run options to a container configuration -func (r *Opts) ToContainerConfig(image string) (containers.ContainerConfig, error) { - if r.Name == "" { - r.Name = getRandomName() - } - - publish, err := r.toPorts() - if err != nil { - return containers.ContainerConfig{}, err - } - - labels, err := toLabels(r.Labels) - if err != nil { - return containers.ContainerConfig{}, err - } - - restartPolicy, err := toRestartPolicy(r.RestartPolicyCondition) - if err != nil { - return containers.ContainerConfig{}, err - } - - envVars := r.Environment - for _, f := range r.EnvironmentFiles { - vars, err := opts.ParseEnvFile(f) - if err != nil { - return containers.ContainerConfig{}, err - } - envVars = append(envVars, vars...) - } - - var platform *specs.Platform - - if r.Platform != "" { - p, err := platforms.Parse(r.Platform) - if err != nil { - return containers.ContainerConfig{}, err - } - platform = &p - } - - return containers.ContainerConfig{ - ID: r.Name, - Image: image, - Command: r.Command, - Ports: publish, - Labels: labels, - Volumes: r.Volumes, - MemLimit: r.Memory, - CPULimit: r.Cpus, - Environment: envVars, - RestartPolicyCondition: restartPolicy, - DomainName: r.DomainName, - AutoRemove: r.Rm, - Healthcheck: r.toHealthcheck(), - Platform: platform, - }, nil -} - -func (r *Opts) toHealthcheck() containers.Healthcheck { - var healthCmd []string - - if len(r.HealthCmd) > 0 { - healthCmd = strings.Split(r.HealthCmd, " ") - } - return containers.Healthcheck{ - Disable: len(healthCmd) == 0, - Test: healthCmd, - Interval: types.Duration(r.HealthInterval), - StartPeriod: types.Duration(r.HealthStartPeriod), - Timeout: types.Duration(r.HealthTimeout), - Retries: r.HealthRetries, - } -} - -var restartPolicyMap = map[string]string{ - "": containers.RestartPolicyNone, - containers.RestartPolicyNone: containers.RestartPolicyNone, - containers.RestartPolicyAny: containers.RestartPolicyAny, - containers.RestartPolicyOnFailure: containers.RestartPolicyOnFailure, - - containers.RestartPolicyRunNo: containers.RestartPolicyNone, - containers.RestartPolicyRunAlways: containers.RestartPolicyAny, -} - -func toRestartPolicy(value string) (string, error) { - value, ok := restartPolicyMap[value] - if !ok { - return "", fmt.Errorf("invalid restart value, must be one of %s", strings.Join(RestartPolicyList, ", ")) - } - return value, nil -} - -func (r *Opts) toPorts() ([]containers.Port, error) { - _, bindings, err := nat.ParsePortSpecs(r.Publish) - if err != nil { - return nil, err - } - var result []containers.Port - - for port, bind := range bindings { - for _, portbind := range bind { - var hostPort uint32 - if portbind.HostPort != "" { - hp, err := strconv.Atoi(portbind.HostPort) - if err != nil { - return nil, err - } - hostPort = uint32(hp) - } else { - hostPort = uint32(port.Int()) - } - - result = append(result, containers.Port{ - HostPort: hostPort, - ContainerPort: uint32(port.Int()), - Protocol: port.Proto(), - HostIP: portbind.HostIP, - }) - } - } - - return result, nil -} - -func toLabels(labels []string) (map[string]string, error) { - result := map[string]string{} - for _, label := range labels { - parts := strings.Split(label, "=") - if len(parts) != 2 { - return nil, fmt.Errorf("wrong label format %q", label) - } - result[parts[0]] = parts[1] - } - - return result, nil -} - -func getRandomName() string { - // Azure supports hyphen but not underscore in names - return strings.Replace(namesgenerator.GetRandomName(0), "_", "-", -1) -} diff --git a/cli/options/run/opts_test.go b/cli/options/run/opts_test.go deleted file mode 100644 index 639a71f93..000000000 --- a/cli/options/run/opts_test.go +++ /dev/null @@ -1,259 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package run - -import ( - "errors" - "regexp" - "testing" - "time" - - "github.com/compose-spec/compose-go/types" - "github.com/google/go-cmp/cmp/cmpopts" - "gotest.tools/v3/assert" - "gotest.tools/v3/assert/cmp" - - "github.com/docker/compose-cli/api/containers" -) - -var ( - // AzureNameRegex is used to validate container names - // Regex was taken from server side error: - // The container name must contain no more than 63 characters and must match the regex '[a-z0-9]([-a-z0-9]*[a-z0-9])?' (e.g. 'my-name'). - AzureNameRegex = regexp.MustCompile("[a-z0-9]([-a-z0-9]*[a-z0-9])") -) - -// TestAzureRandomName ensures compliance with Azure naming requirements -func TestAzureRandomName(t *testing.T) { - n := getRandomName() - assert.Assert(t, len(n) < 64) - assert.Assert(t, len(n) > 1) - assert.Assert(t, cmp.Regexp(AzureNameRegex, n)) -} - -func TestPortParse(t *testing.T) { - testCases := []struct { - in string - expected []containers.Port - }{ - { - in: "80", - expected: []containers.Port{ - { - HostPort: 80, - ContainerPort: 80, - Protocol: "tcp", - }, - }, - }, - { - in: "80:80", - expected: []containers.Port{ - { - HostPort: 80, - ContainerPort: 80, - Protocol: "tcp", - }, - }, - }, - { - in: "80:80/udp", - expected: []containers.Port{ - { - ContainerPort: 80, - HostPort: 80, - Protocol: "udp", - }, - }, - }, - { - in: "8080:80", - expected: []containers.Port{ - { - HostPort: 8080, - ContainerPort: 80, - Protocol: "tcp", - }, - }, - }, - { - in: "192.168.0.2:8080:80", - expected: []containers.Port{ - { - HostPort: 8080, - ContainerPort: 80, - Protocol: "tcp", - HostIP: "192.168.0.2", - }, - }, - }, - { - in: "80-81:80-81", - expected: []containers.Port{ - { - HostPort: 80, - ContainerPort: 80, - Protocol: "tcp", - }, - { - HostPort: 81, - ContainerPort: 81, - Protocol: "tcp", - }, - }, - }, - } - - for _, testCase := range testCases { - opts := Opts{ - Publish: []string{testCase.in}, - } - result, err := opts.toPorts() - assert.NilError(t, err) - assert.DeepEqual(t, result, testCase.expected, cmpopts.SortSlices(func(x, y containers.Port) bool { - return x.ContainerPort < y.ContainerPort - })) - } -} - -func TestLabels(t *testing.T) { - testCases := []struct { - in []string - expected map[string]string - expectedError error - }{ - { - in: []string{"label=value"}, - expected: map[string]string{ - "label": "value", - }, - expectedError: nil, - }, - { - in: []string{"label=value", "label=value2"}, - expected: map[string]string{ - "label": "value2", - }, - expectedError: nil, - }, - { - in: []string{"label=value", "label2=value2"}, - expected: map[string]string{ - "label": "value", - "label2": "value2", - }, - expectedError: nil, - }, - { - in: []string{"label"}, - expected: nil, - expectedError: errors.New(`wrong label format "label"`), - }, - } - - for _, testCase := range testCases { - result, err := toLabels(testCase.in) - if testCase.expectedError == nil { - assert.NilError(t, err) - } else { - assert.Error(t, err, testCase.expectedError.Error()) - } - assert.DeepEqual(t, result, testCase.expected) - } -} - -func TestValidateRestartPolicy(t *testing.T) { - testCases := []struct { - in string - expected string - expectedError error - }{ - { - in: "none", - expected: "none", - expectedError: nil, - }, - { - in: "any", - expected: "any", - expectedError: nil, - }, - { - in: "on-failure", - expected: "on-failure", - expectedError: nil, - }, - { - in: "", - expected: "none", - expectedError: nil, - }, - { - in: "no", - expected: "none", - expectedError: nil, - }, - { - in: "always", - expected: "any", - expectedError: nil, - }, - - { - in: "toto", - expected: "", - expectedError: errors.New("invalid restart value, must be one of no, always, on-failure"), - }, - } - for _, testCase := range testCases { - result, err := toRestartPolicy(testCase.in) - if testCase.expectedError == nil { - assert.NilError(t, err) - } else { - assert.Error(t, err, testCase.expectedError.Error()) - } - assert.Equal(t, testCase.expected, result) - } -} - -func TestToHealthcheck(t *testing.T) { - testOpt := Opts{ - HealthCmd: "curl", - } - - assert.DeepEqual(t, testOpt.toHealthcheck(), containers.Healthcheck{ - Disable: false, - Test: []string{"curl"}, - }) - - testOpt = Opts{ - HealthCmd: "curl", - HealthRetries: 3, - HealthInterval: 5 * time.Second, - HealthTimeout: 2 * time.Second, - HealthStartPeriod: 10 * time.Second, - } - - assert.DeepEqual(t, testOpt.toHealthcheck(), containers.Healthcheck{ - Disable: false, - Test: []string{"curl"}, - Retries: 3, - Interval: types.Duration(5 * time.Second), - StartPeriod: types.Duration(10 * time.Second), - Timeout: types.Duration(2 * time.Second), - }) -} diff --git a/cli/server/contextserverstream.go b/cli/server/contextserverstream.go deleted file mode 100644 index d3f31520e..000000000 --- a/cli/server/contextserverstream.go +++ /dev/null @@ -1,57 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package server - -import ( - "context" - - "google.golang.org/grpc" - "google.golang.org/grpc/metadata" -) - -// A gRPC server stream will only let you get its context but -// there is no way to set a new (augmented context) to the next -// handler (like we do for a unary request). We need to wrap the grpc.ServerSteam -// to be able to set a new context that will be sent to the next stream interceptor. -type contextServerStream struct { - ss grpc.ServerStream - ctx context.Context -} - -func (css *contextServerStream) SetHeader(md metadata.MD) error { - return css.ss.SetHeader(md) -} - -func (css *contextServerStream) SendHeader(md metadata.MD) error { - return css.ss.SendHeader(md) -} - -func (css *contextServerStream) SetTrailer(md metadata.MD) { - css.ss.SetTrailer(md) -} - -func (css *contextServerStream) Context() context.Context { - return css.ctx -} - -func (css *contextServerStream) SendMsg(m interface{}) error { - return css.ss.SendMsg(m) -} - -func (css *contextServerStream) RecvMsg(m interface{}) error { - return css.ss.RecvMsg(m) -} diff --git a/cli/server/interceptor.go b/cli/server/interceptor.go deleted file mode 100644 index 617721a7d..000000000 --- a/cli/server/interceptor.go +++ /dev/null @@ -1,124 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package server - -import ( - "context" - "errors" - "strings" - - "google.golang.org/grpc" - "google.golang.org/grpc/metadata" - - "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/api/config" - apicontext "github.com/docker/compose-cli/api/context" - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/cli/server/proxy" -) - -// key is the key where the current docker context is stored in the metadata -// of a gRPC request -const key = "context_key" - -// unaryServerInterceptor configures the context and sends it to the next handler -func unaryServerInterceptor(clictx context.Context) grpc.UnaryServerInterceptor { - return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - currentContext, err := getIncomingContext(ctx) - if err != nil { - currentContext, err = getConfigContext() - if err != nil { - return nil, err - } - } - configuredCtx, err := configureContext(clictx, currentContext, info.FullMethod) - if err != nil { - return nil, err - } - - return handler(configuredCtx, req) - } -} - -// streamServerInterceptor configures the context and sends it to the next handler -func streamServerInterceptor(clictx context.Context) grpc.StreamServerInterceptor { - return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { - currentContext, err := getIncomingContext(ss.Context()) - if err != nil { - currentContext, err = getConfigContext() - if err != nil { - return err - } - } - ctx, err := configureContext(clictx, currentContext, info.FullMethod) - if err != nil { - return err - } - - return handler(srv, &contextServerStream{ - ss: ss, - ctx: ctx, - }) - } -} - -// Returns the current context from the configuration file -func getConfigContext() (string, error) { - configDir := config.Dir() - configFile, err := config.LoadFile(configDir) - if err != nil { - return "", err - } - return configFile.CurrentContext, nil -} - -// Returns the context set by the caller if any, error otherwise -func getIncomingContext(ctx context.Context) (string, error) { - if md, ok := metadata.FromIncomingContext(ctx); ok { - if key, ok := md[key]; ok { - return key[0], nil - } - } - - return "", errors.New("not found") -} - -// configureContext populates the request context with objects the client -// needs: the context store and the api client -func configureContext(ctx context.Context, currentContext string, method string) (context.Context, error) { - configDir := config.Dir() - - apicontext.WithCurrentContext(currentContext) - - // The contexts service doesn't need the client - if !strings.Contains(method, "/com.docker.api.protos.context.v1.Contexts") { - c, err := client.New(ctx) - if err != nil { - return nil, err - } - - ctx = proxy.WithClient(ctx, c) - } - - s, err := store.New(configDir) - if err != nil { - return nil, err - } - store.WithContextStore(s) - - return ctx, nil -} diff --git a/cli/server/interceptor_test.go b/cli/server/interceptor_test.go deleted file mode 100644 index 3bc401a5c..000000000 --- a/cli/server/interceptor_test.go +++ /dev/null @@ -1,125 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package server - -import ( - "context" - "io/ioutil" - "os" - "path" - "testing" - - "google.golang.org/grpc" - "google.golang.org/grpc/metadata" - "gotest.tools/v3/assert" - "gotest.tools/v3/assert/cmp" - - "github.com/docker/compose-cli/api/config" - apicontext "github.com/docker/compose-cli/api/context" -) - -func testContext(t *testing.T) context.Context { - dir, err := ioutil.TempDir("", "example") - assert.NilError(t, err) - - t.Cleanup(func() { - _ = os.RemoveAll(dir) - }) - - ctx := context.Background() - config.WithDir(dir) - err = ioutil.WriteFile(path.Join(dir, "config.json"), []byte(`{"currentContext": "default"}`), 0644) - assert.NilError(t, err) - - return ctx -} - -func TestUnaryGetCurrentContext(t *testing.T) { - ctx := testContext(t) - interceptor := unaryServerInterceptor(ctx) - - currentContext := callUnary(context.Background(), t, interceptor) - assert.Equal(t, currentContext, "default") -} - -func TestUnaryContextFromMetadata(t *testing.T) { - ctx := testContext(t) - contextName := "test" - - interceptor := unaryServerInterceptor(ctx) - reqCtx := context.Background() - reqCtx = metadata.NewIncomingContext(reqCtx, metadata.MD{ - (key): []string{contextName}, - }) - - currentContext := callUnary(reqCtx, t, interceptor) - assert.Equal(t, contextName, currentContext) -} - -func TestStreamGetCurrentContext(t *testing.T) { - ctx := testContext(t) - interceptor := streamServerInterceptor(ctx) - - currentContext := callStream(context.Background(), t, interceptor) - - assert.Equal(t, currentContext, "default") -} - -func TestStreamContextFromMetadata(t *testing.T) { - ctx := testContext(t) - contextName := "test" - - interceptor := streamServerInterceptor(ctx) - reqCtx := context.Background() - reqCtx = metadata.NewIncomingContext(reqCtx, metadata.MD{ - (key): []string{contextName}, - }) - - currentContext := callStream(reqCtx, t, interceptor) - assert.Equal(t, currentContext, contextName) -} - -func callStream(ctx context.Context, t *testing.T, interceptor grpc.StreamServerInterceptor) string { - currentContext := "" - err := interceptor(nil, &contextServerStream{ - ctx: ctx, - }, &grpc.StreamServerInfo{ - FullMethod: "/com.docker.api.protos.context.v1.Contexts/test", - }, func(srv interface{}, stream grpc.ServerStream) error { - currentContext = apicontext.Current() - return nil - }) - - assert.NilError(t, err) - - return currentContext -} - -func callUnary(ctx context.Context, t *testing.T, interceptor grpc.UnaryServerInterceptor) string { - currentContext := "" - resp, err := interceptor(ctx, nil, &grpc.UnaryServerInfo{ - FullMethod: "/com.docker.api.protos.context.v1.Contexts/test", - }, func(ctx context.Context, req interface{}) (interface{}, error) { - currentContext = apicontext.Current() - return nil, nil - }) - - assert.NilError(t, err) - assert.Assert(t, cmp.Nil(resp)) - - return currentContext -} diff --git a/cli/server/metrics.go b/cli/server/metrics.go deleted file mode 100644 index 439eb69a9..000000000 --- a/cli/server/metrics.go +++ /dev/null @@ -1,79 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package server - -import ( - "context" - - "google.golang.org/grpc" - - "github.com/docker/compose-cli/cli/metrics" - "github.com/docker/compose-cli/cli/server/proxy" - "github.com/docker/compose-cli/pkg/compose" -) - -var ( - methodMapping = map[string]string{ - "/com.docker.api.protos.containers.v1.Containers/List": "ps", - "/com.docker.api.protos.containers.v1.Containers/Start": "start", - "/com.docker.api.protos.containers.v1.Containers/Stop": "stop", - "/com.docker.api.protos.containers.v1.Containers/Run": "run", - "/com.docker.api.protos.containers.v1.Containers/Exec": "exec", - "/com.docker.api.protos.containers.v1.Containers/Delete": "rm", - "/com.docker.api.protos.containers.v1.Containers/Kill": "kill", - "/com.docker.api.protos.containers.v1.Containers/Inspect": "inspect", - "/com.docker.api.protos.containers.v1.Containers/Logs": "logs", - "/com.docker.api.protos.streams.v1.Streaming/NewStream": "streaming", - "/com.docker.api.protos.context.v1.Contexts/List": "context ls", - "/com.docker.api.protos.context.v1.Contexts/SetCurrent": "context use", - "/com.docker.api.protos.volumes.v1.Volumes/VolumesList": "volume ls", - "/com.docker.api.protos.volumes.v1.Volumes/VolumesDelete": "volume rm", - "/com.docker.api.protos.volumes.v1.Volumes/VolumesCreate": "volume create", - "/com.docker.api.protos.volumes.v1.Volumes/VolumesInspect": "volume inspect", - "/com.docker.api.protos.compose.v1.Compose/Up": "compose up", - "/com.docker.api.protos.compose.v1.Compose/Down": "compose down", - "/com.docker.api.protos.compose.v1.Compose/Stacks": "compose ls", - "/com.docker.api.protos.compose.v1.Compose/Services": "compose ps", - } -) - -func metricsServerInterceptor(client metrics.Client) grpc.UnaryServerInterceptor { - return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - backendClient := proxy.Client(ctx) - contextType := "" - if backendClient != nil { - contextType = backendClient.ContextType() - } - - data, err := handler(ctx, req) - - status := compose.SuccessStatus - if err != nil { - status = compose.FailureStatus - } - command := methodMapping[info.FullMethod] - if command != "" { - client.Send(metrics.Command{ - Command: command, - Context: contextType, - Source: compose.APISource, - Status: status, - }) - } - return data, err - } -} diff --git a/cli/server/metrics_test.go b/cli/server/metrics_test.go deleted file mode 100644 index 8c4501e2f..000000000 --- a/cli/server/metrics_test.go +++ /dev/null @@ -1,128 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package server - -import ( - "context" - "strings" - "testing" - - "github.com/docker/compose-cli/api/resources" - - "github.com/stretchr/testify/mock" - "google.golang.org/grpc" - "google.golang.org/grpc/metadata" - "gotest.tools/v3/assert" - - "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/api/containers" - "github.com/docker/compose-cli/api/secrets" - "github.com/docker/compose-cli/api/volumes" - "github.com/docker/compose-cli/cli/metrics" - containersv1 "github.com/docker/compose-cli/cli/server/protos/containers/v1" - contextsv1 "github.com/docker/compose-cli/cli/server/protos/contexts/v1" - streamsv1 "github.com/docker/compose-cli/cli/server/protos/streams/v1" - volumesv1 "github.com/docker/compose-cli/cli/server/protos/volumes/v1" - "github.com/docker/compose-cli/cli/server/proxy" - "github.com/docker/compose-cli/pkg/api" -) - -func TestAllMethodsHaveCorrespondingCliCommand(t *testing.T) { - s := setupServer() - i := s.GetServiceInfo() - for k, v := range i { - if k == "grpc.health.v1.Health" { - continue - } - var errs []string - for _, m := range v.Methods { - name := "/" + k + "/" + m.Name - if _, keyExists := methodMapping[name]; !keyExists { - errs = append(errs, name+" not mapped to a corresponding cli command") - } - } - assert.Equal(t, "", strings.Join(errs, "\n")) - } -} - -func TestTrackSuccess(t *testing.T) { - var mockMetrics = &mockMetricsClient{} - mockMetrics.On("Send", metrics.Command{Command: "ps", Context: "aci", Status: "success", Source: "api"}).Return() - newClient := client.NewClient("aci", noopService{}) - interceptor := metricsServerInterceptor(mockMetrics) - - ctx := proxy.WithClient(incomingContext("acicontext"), &newClient) - _, err := interceptor(ctx, nil, containerMethodRoute("List"), mockHandler(nil)) - assert.NilError(t, err) -} - -func TestTrackSFailures(t *testing.T) { - var mockMetrics = &mockMetricsClient{} - newClient := client.NewClient("moby", noopService{}) - interceptor := metricsServerInterceptor(mockMetrics) - - ctx := proxy.WithClient(incomingContext("default"), &newClient) - _, err := interceptor(ctx, nil, containerMethodRoute("Create"), mockHandler(api.ErrLoginRequired)) - assert.Assert(t, err == api.ErrLoginRequired) -} - -func containerMethodRoute(action string) *grpc.UnaryServerInfo { - var info = &grpc.UnaryServerInfo{ - FullMethod: "/com.docker.api.protos.containers.v1.Containers/" + action, - } - return info -} - -func mockHandler(err error) func(ctx context.Context, req interface{}) (interface{}, error) { - return func(ctx context.Context, req interface{}) (interface{}, error) { - return nil, err - } -} - -func incomingContext(status string) context.Context { - ctx := metadata.NewIncomingContext(context.TODO(), metadata.MD{ - (key): []string{status}, - }) - return ctx -} - -func setupServer() *grpc.Server { - ctx := context.TODO() - s := New(ctx) - p := proxy.New(ctx) - containersv1.RegisterContainersServer(s, p) - streamsv1.RegisterStreamingServer(s, p) - volumesv1.RegisterVolumesServer(s, p) - contextsv1.RegisterContextsServer(s, p.ContextsProxy()) - return s -} - -type noopService struct{} - -func (noopService) ContainerService() containers.Service { return nil } -func (noopService) ComposeService() api.Service { return nil } -func (noopService) SecretsService() secrets.Service { return nil } -func (noopService) VolumeService() volumes.Service { return nil } -func (noopService) ResourceService() resources.Service { return nil } - -type mockMetricsClient struct { - mock.Mock -} - -func (s *mockMetricsClient) Send(command metrics.Command) { - s.Called(command) -} diff --git a/cli/server/protos/containers/v1/containers.pb.go b/cli/server/protos/containers/v1/containers.pb.go deleted file mode 100644 index 32a88513b..000000000 --- a/cli/server/protos/containers/v1/containers.pb.go +++ /dev/null @@ -1,2367 +0,0 @@ -// -// Copyright 2020 Docker Compose CLI authors - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.25.0 -// protoc v3.13.0 -// source: cli/server/protos/containers/v1/containers.proto - -package v1 - -import ( - context "context" - proto "github.com/golang/protobuf/proto" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// This is a compile-time assertion that a sufficiently up-to-date version -// of the legacy proto package is being used. -const _ = proto.ProtoPackageIsVersion4 - -type Port struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - HostPort uint32 `protobuf:"varint,1,opt,name=host_port,json=hostPort,proto3" json:"host_port,omitempty"` - ContainerPort uint32 `protobuf:"varint,2,opt,name=container_port,json=containerPort,proto3" json:"container_port,omitempty"` - Protocol string `protobuf:"bytes,3,opt,name=protocol,proto3" json:"protocol,omitempty"` - HostIp string `protobuf:"bytes,4,opt,name=host_ip,json=hostIp,proto3" json:"host_ip,omitempty"` -} - -func (x *Port) Reset() { - *x = Port{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Port) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Port) ProtoMessage() {} - -func (x *Port) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Port.ProtoReflect.Descriptor instead. -func (*Port) Descriptor() ([]byte, []int) { - return file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP(), []int{0} -} - -func (x *Port) GetHostPort() uint32 { - if x != nil { - return x.HostPort - } - return 0 -} - -func (x *Port) GetContainerPort() uint32 { - if x != nil { - return x.ContainerPort - } - return 0 -} - -func (x *Port) GetProtocol() string { - if x != nil { - return x.Protocol - } - return "" -} - -func (x *Port) GetHostIp() string { - if x != nil { - return x.HostIp - } - return "" -} - -type Container struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Image string `protobuf:"bytes,2,opt,name=image,proto3" json:"image,omitempty"` - Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` - Command string `protobuf:"bytes,4,opt,name=command,proto3" json:"command,omitempty"` - CpuTime uint64 `protobuf:"varint,5,opt,name=cpu_time,json=cpuTime,proto3" json:"cpu_time,omitempty"` - MemoryUsage uint64 `protobuf:"varint,6,opt,name=memory_usage,json=memoryUsage,proto3" json:"memory_usage,omitempty"` - PidsCurrent uint64 `protobuf:"varint,8,opt,name=pids_current,json=pidsCurrent,proto3" json:"pids_current,omitempty"` - PidsLimit uint64 `protobuf:"varint,9,opt,name=pids_limit,json=pidsLimit,proto3" json:"pids_limit,omitempty"` - Labels []string `protobuf:"bytes,10,rep,name=labels,proto3" json:"labels,omitempty"` - Ports []*Port `protobuf:"bytes,11,rep,name=ports,proto3" json:"ports,omitempty"` - Platform string `protobuf:"bytes,13,opt,name=platform,proto3" json:"platform,omitempty"` - HostConfig *HostConfig `protobuf:"bytes,15,opt,name=host_config,json=hostConfig,proto3" json:"host_config,omitempty"` - Healthcheck *Healthcheck `protobuf:"bytes,16,opt,name=healthcheck,proto3" json:"healthcheck,omitempty"` -} - -func (x *Container) Reset() { - *x = Container{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Container) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Container) ProtoMessage() {} - -func (x *Container) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Container.ProtoReflect.Descriptor instead. -func (*Container) Descriptor() ([]byte, []int) { - return file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP(), []int{1} -} - -func (x *Container) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *Container) GetImage() string { - if x != nil { - return x.Image - } - return "" -} - -func (x *Container) GetStatus() string { - if x != nil { - return x.Status - } - return "" -} - -func (x *Container) GetCommand() string { - if x != nil { - return x.Command - } - return "" -} - -func (x *Container) GetCpuTime() uint64 { - if x != nil { - return x.CpuTime - } - return 0 -} - -func (x *Container) GetMemoryUsage() uint64 { - if x != nil { - return x.MemoryUsage - } - return 0 -} - -func (x *Container) GetPidsCurrent() uint64 { - if x != nil { - return x.PidsCurrent - } - return 0 -} - -func (x *Container) GetPidsLimit() uint64 { - if x != nil { - return x.PidsLimit - } - return 0 -} - -func (x *Container) GetLabels() []string { - if x != nil { - return x.Labels - } - return nil -} - -func (x *Container) GetPorts() []*Port { - if x != nil { - return x.Ports - } - return nil -} - -func (x *Container) GetPlatform() string { - if x != nil { - return x.Platform - } - return "" -} - -func (x *Container) GetHostConfig() *HostConfig { - if x != nil { - return x.HostConfig - } - return nil -} - -func (x *Container) GetHealthcheck() *Healthcheck { - if x != nil { - return x.Healthcheck - } - return nil -} - -type HostConfig struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - MemoryReservation uint64 `protobuf:"varint,1,opt,name=memory_reservation,json=memoryReservation,proto3" json:"memory_reservation,omitempty"` - MemoryLimit uint64 `protobuf:"varint,2,opt,name=memory_limit,json=memoryLimit,proto3" json:"memory_limit,omitempty"` - CpuReservation uint64 `protobuf:"varint,3,opt,name=cpu_reservation,json=cpuReservation,proto3" json:"cpu_reservation,omitempty"` - CpuLimit uint64 `protobuf:"varint,4,opt,name=cpu_limit,json=cpuLimit,proto3" json:"cpu_limit,omitempty"` - RestartPolicy string `protobuf:"bytes,5,opt,name=restart_policy,json=restartPolicy,proto3" json:"restart_policy,omitempty"` - AutoRemove bool `protobuf:"varint,6,opt,name=auto_remove,json=autoRemove,proto3" json:"auto_remove,omitempty"` -} - -func (x *HostConfig) Reset() { - *x = HostConfig{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *HostConfig) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*HostConfig) ProtoMessage() {} - -func (x *HostConfig) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use HostConfig.ProtoReflect.Descriptor instead. -func (*HostConfig) Descriptor() ([]byte, []int) { - return file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP(), []int{2} -} - -func (x *HostConfig) GetMemoryReservation() uint64 { - if x != nil { - return x.MemoryReservation - } - return 0 -} - -func (x *HostConfig) GetMemoryLimit() uint64 { - if x != nil { - return x.MemoryLimit - } - return 0 -} - -func (x *HostConfig) GetCpuReservation() uint64 { - if x != nil { - return x.CpuReservation - } - return 0 -} - -func (x *HostConfig) GetCpuLimit() uint64 { - if x != nil { - return x.CpuLimit - } - return 0 -} - -func (x *HostConfig) GetRestartPolicy() string { - if x != nil { - return x.RestartPolicy - } - return "" -} - -func (x *HostConfig) GetAutoRemove() bool { - if x != nil { - return x.AutoRemove - } - return false -} - -type Healthcheck struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Disable bool `protobuf:"varint,1,opt,name=disable,proto3" json:"disable,omitempty"` - Test []string `protobuf:"bytes,2,rep,name=test,proto3" json:"test,omitempty"` - Interval int64 `protobuf:"varint,3,opt,name=interval,proto3" json:"interval,omitempty"` -} - -func (x *Healthcheck) Reset() { - *x = Healthcheck{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Healthcheck) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Healthcheck) ProtoMessage() {} - -func (x *Healthcheck) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Healthcheck.ProtoReflect.Descriptor instead. -func (*Healthcheck) Descriptor() ([]byte, []int) { - return file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP(), []int{3} -} - -func (x *Healthcheck) GetDisable() bool { - if x != nil { - return x.Disable - } - return false -} - -func (x *Healthcheck) GetTest() []string { - if x != nil { - return x.Test - } - return nil -} - -func (x *Healthcheck) GetInterval() int64 { - if x != nil { - return x.Interval - } - return 0 -} - -type InspectRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` -} - -func (x *InspectRequest) Reset() { - *x = InspectRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *InspectRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*InspectRequest) ProtoMessage() {} - -func (x *InspectRequest) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use InspectRequest.ProtoReflect.Descriptor instead. -func (*InspectRequest) Descriptor() ([]byte, []int) { - return file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP(), []int{4} -} - -func (x *InspectRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -type InspectResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Container *Container `protobuf:"bytes,1,opt,name=container,proto3" json:"container,omitempty"` -} - -func (x *InspectResponse) Reset() { - *x = InspectResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *InspectResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*InspectResponse) ProtoMessage() {} - -func (x *InspectResponse) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use InspectResponse.ProtoReflect.Descriptor instead. -func (*InspectResponse) Descriptor() ([]byte, []int) { - return file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP(), []int{5} -} - -func (x *InspectResponse) GetContainer() *Container { - if x != nil { - return x.Container - } - return nil -} - -type DeleteRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Force bool `protobuf:"varint,2,opt,name=force,proto3" json:"force,omitempty"` -} - -func (x *DeleteRequest) Reset() { - *x = DeleteRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *DeleteRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DeleteRequest) ProtoMessage() {} - -func (x *DeleteRequest) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DeleteRequest.ProtoReflect.Descriptor instead. -func (*DeleteRequest) Descriptor() ([]byte, []int) { - return file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP(), []int{6} -} - -func (x *DeleteRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *DeleteRequest) GetForce() bool { - if x != nil { - return x.Force - } - return false -} - -type DeleteResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *DeleteResponse) Reset() { - *x = DeleteResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *DeleteResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DeleteResponse) ProtoMessage() {} - -func (x *DeleteResponse) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DeleteResponse.ProtoReflect.Descriptor instead. -func (*DeleteResponse) Descriptor() ([]byte, []int) { - return file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP(), []int{7} -} - -type StartRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` -} - -func (x *StartRequest) Reset() { - *x = StartRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *StartRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*StartRequest) ProtoMessage() {} - -func (x *StartRequest) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use StartRequest.ProtoReflect.Descriptor instead. -func (*StartRequest) Descriptor() ([]byte, []int) { - return file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP(), []int{8} -} - -func (x *StartRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -type StartResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *StartResponse) Reset() { - *x = StartResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *StartResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*StartResponse) ProtoMessage() {} - -func (x *StartResponse) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use StartResponse.ProtoReflect.Descriptor instead. -func (*StartResponse) Descriptor() ([]byte, []int) { - return file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP(), []int{9} -} - -type StopRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Timeout uint32 `protobuf:"varint,2,opt,name=timeout,proto3" json:"timeout,omitempty"` -} - -func (x *StopRequest) Reset() { - *x = StopRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *StopRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*StopRequest) ProtoMessage() {} - -func (x *StopRequest) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use StopRequest.ProtoReflect.Descriptor instead. -func (*StopRequest) Descriptor() ([]byte, []int) { - return file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP(), []int{10} -} - -func (x *StopRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *StopRequest) GetTimeout() uint32 { - if x != nil { - return x.Timeout - } - return 0 -} - -type StopResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *StopResponse) Reset() { - *x = StopResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *StopResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*StopResponse) ProtoMessage() {} - -func (x *StopResponse) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use StopResponse.ProtoReflect.Descriptor instead. -func (*StopResponse) Descriptor() ([]byte, []int) { - return file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP(), []int{11} -} - -type KillRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Signal string `protobuf:"bytes,2,opt,name=signal,proto3" json:"signal,omitempty"` -} - -func (x *KillRequest) Reset() { - *x = KillRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *KillRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*KillRequest) ProtoMessage() {} - -func (x *KillRequest) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[12] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use KillRequest.ProtoReflect.Descriptor instead. -func (*KillRequest) Descriptor() ([]byte, []int) { - return file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP(), []int{12} -} - -func (x *KillRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *KillRequest) GetSignal() string { - if x != nil { - return x.Signal - } - return "" -} - -type KillResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *KillResponse) Reset() { - *x = KillResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *KillResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*KillResponse) ProtoMessage() {} - -func (x *KillResponse) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[13] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use KillResponse.ProtoReflect.Descriptor instead. -func (*KillResponse) Descriptor() ([]byte, []int) { - return file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP(), []int{13} -} - -type RunRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Image string `protobuf:"bytes,2,opt,name=image,proto3" json:"image,omitempty"` - Ports []*Port `protobuf:"bytes,3,rep,name=ports,proto3" json:"ports,omitempty"` - Labels map[string]string `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - Volumes []string `protobuf:"bytes,5,rep,name=volumes,proto3" json:"volumes,omitempty"` - MemoryLimit uint64 `protobuf:"varint,6,opt,name=memory_limit,json=memoryLimit,proto3" json:"memory_limit,omitempty"` - CpuLimit uint64 `protobuf:"varint,7,opt,name=cpu_limit,json=cpuLimit,proto3" json:"cpu_limit,omitempty"` - RestartPolicyCondition string `protobuf:"bytes,8,opt,name=restart_policy_condition,json=restartPolicyCondition,proto3" json:"restart_policy_condition,omitempty"` - Command []string `protobuf:"bytes,9,rep,name=command,proto3" json:"command,omitempty"` - Environment []string `protobuf:"bytes,10,rep,name=environment,proto3" json:"environment,omitempty"` - AutoRemove bool `protobuf:"varint,11,opt,name=auto_remove,json=autoRemove,proto3" json:"auto_remove,omitempty"` - Healthcheck *Healthcheck `protobuf:"bytes,12,opt,name=healthcheck,proto3" json:"healthcheck,omitempty"` - Platform string `protobuf:"bytes,13,opt,name=platform,proto3" json:"platform,omitempty"` -} - -func (x *RunRequest) Reset() { - *x = RunRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *RunRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RunRequest) ProtoMessage() {} - -func (x *RunRequest) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[14] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RunRequest.ProtoReflect.Descriptor instead. -func (*RunRequest) Descriptor() ([]byte, []int) { - return file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP(), []int{14} -} - -func (x *RunRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *RunRequest) GetImage() string { - if x != nil { - return x.Image - } - return "" -} - -func (x *RunRequest) GetPorts() []*Port { - if x != nil { - return x.Ports - } - return nil -} - -func (x *RunRequest) GetLabels() map[string]string { - if x != nil { - return x.Labels - } - return nil -} - -func (x *RunRequest) GetVolumes() []string { - if x != nil { - return x.Volumes - } - return nil -} - -func (x *RunRequest) GetMemoryLimit() uint64 { - if x != nil { - return x.MemoryLimit - } - return 0 -} - -func (x *RunRequest) GetCpuLimit() uint64 { - if x != nil { - return x.CpuLimit - } - return 0 -} - -func (x *RunRequest) GetRestartPolicyCondition() string { - if x != nil { - return x.RestartPolicyCondition - } - return "" -} - -func (x *RunRequest) GetCommand() []string { - if x != nil { - return x.Command - } - return nil -} - -func (x *RunRequest) GetEnvironment() []string { - if x != nil { - return x.Environment - } - return nil -} - -func (x *RunRequest) GetAutoRemove() bool { - if x != nil { - return x.AutoRemove - } - return false -} - -func (x *RunRequest) GetHealthcheck() *Healthcheck { - if x != nil { - return x.Healthcheck - } - return nil -} - -func (x *RunRequest) GetPlatform() string { - if x != nil { - return x.Platform - } - return "" -} - -type RunResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *RunResponse) Reset() { - *x = RunResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *RunResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RunResponse) ProtoMessage() {} - -func (x *RunResponse) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[15] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RunResponse.ProtoReflect.Descriptor instead. -func (*RunResponse) Descriptor() ([]byte, []int) { - return file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP(), []int{15} -} - -type ExecRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Command string `protobuf:"bytes,2,opt,name=command,proto3" json:"command,omitempty"` - StreamId string `protobuf:"bytes,3,opt,name=stream_id,json=streamId,proto3" json:"stream_id,omitempty"` - Args []string `protobuf:"bytes,4,rep,name=args,proto3" json:"args,omitempty"` - Env []string `protobuf:"bytes,5,rep,name=env,proto3" json:"env,omitempty"` - Tty bool `protobuf:"varint,6,opt,name=tty,proto3" json:"tty,omitempty"` -} - -func (x *ExecRequest) Reset() { - *x = ExecRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ExecRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ExecRequest) ProtoMessage() {} - -func (x *ExecRequest) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[16] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ExecRequest.ProtoReflect.Descriptor instead. -func (*ExecRequest) Descriptor() ([]byte, []int) { - return file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP(), []int{16} -} - -func (x *ExecRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *ExecRequest) GetCommand() string { - if x != nil { - return x.Command - } - return "" -} - -func (x *ExecRequest) GetStreamId() string { - if x != nil { - return x.StreamId - } - return "" -} - -func (x *ExecRequest) GetArgs() []string { - if x != nil { - return x.Args - } - return nil -} - -func (x *ExecRequest) GetEnv() []string { - if x != nil { - return x.Env - } - return nil -} - -func (x *ExecRequest) GetTty() bool { - if x != nil { - return x.Tty - } - return false -} - -type ExecResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Output []byte `protobuf:"bytes,1,opt,name=output,proto3" json:"output,omitempty"` -} - -func (x *ExecResponse) Reset() { - *x = ExecResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ExecResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ExecResponse) ProtoMessage() {} - -func (x *ExecResponse) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[17] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ExecResponse.ProtoReflect.Descriptor instead. -func (*ExecResponse) Descriptor() ([]byte, []int) { - return file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP(), []int{17} -} - -func (x *ExecResponse) GetOutput() []byte { - if x != nil { - return x.Output - } - return nil -} - -type ListRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - All bool `protobuf:"varint,1,opt,name=all,proto3" json:"all,omitempty"` -} - -func (x *ListRequest) Reset() { - *x = ListRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[18] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ListRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListRequest) ProtoMessage() {} - -func (x *ListRequest) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[18] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListRequest.ProtoReflect.Descriptor instead. -func (*ListRequest) Descriptor() ([]byte, []int) { - return file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP(), []int{18} -} - -func (x *ListRequest) GetAll() bool { - if x != nil { - return x.All - } - return false -} - -type ListResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Containers []*Container `protobuf:"bytes,1,rep,name=containers,proto3" json:"containers,omitempty"` -} - -func (x *ListResponse) Reset() { - *x = ListResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[19] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ListResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListResponse) ProtoMessage() {} - -func (x *ListResponse) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[19] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListResponse.ProtoReflect.Descriptor instead. -func (*ListResponse) Descriptor() ([]byte, []int) { - return file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP(), []int{19} -} - -func (x *ListResponse) GetContainers() []*Container { - if x != nil { - return x.Containers - } - return nil -} - -type LogsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - ContainerId string `protobuf:"bytes,1,opt,name=container_id,json=containerId,proto3" json:"container_id,omitempty"` - Follow bool `protobuf:"varint,3,opt,name=follow,proto3" json:"follow,omitempty"` -} - -func (x *LogsRequest) Reset() { - *x = LogsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[20] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *LogsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*LogsRequest) ProtoMessage() {} - -func (x *LogsRequest) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[20] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use LogsRequest.ProtoReflect.Descriptor instead. -func (*LogsRequest) Descriptor() ([]byte, []int) { - return file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP(), []int{20} -} - -func (x *LogsRequest) GetContainerId() string { - if x != nil { - return x.ContainerId - } - return "" -} - -func (x *LogsRequest) GetFollow() bool { - if x != nil { - return x.Follow - } - return false -} - -type LogsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Value []byte `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` -} - -func (x *LogsResponse) Reset() { - *x = LogsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[21] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *LogsResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*LogsResponse) ProtoMessage() {} - -func (x *LogsResponse) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_containers_v1_containers_proto_msgTypes[21] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use LogsResponse.ProtoReflect.Descriptor instead. -func (*LogsResponse) Descriptor() ([]byte, []int) { - return file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP(), []int{21} -} - -func (x *LogsResponse) GetValue() []byte { - if x != nil { - return x.Value - } - return nil -} - -var File_cli_server_protos_containers_v1_containers_proto protoreflect.FileDescriptor - -var file_cli_server_protos_containers_v1_containers_proto_rawDesc = []byte{ - 0x0a, 0x30, 0x63, 0x6c, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2f, 0x76, - 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x12, 0x23, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x22, 0x7f, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x12, - 0x1b, 0x0a, 0x09, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x25, 0x0a, 0x0e, - 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x50, - 0x6f, 0x72, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, - 0x17, 0x0a, 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x68, 0x6f, 0x73, 0x74, 0x49, 0x70, 0x22, 0xfe, 0x03, 0x0a, 0x09, 0x43, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x19, - 0x0a, 0x08, 0x63, 0x70, 0x75, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x07, 0x63, 0x70, 0x75, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x65, 0x6d, - 0x6f, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x0b, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0c, - 0x70, 0x69, 0x64, 0x73, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x0b, 0x70, 0x69, 0x64, 0x73, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x12, - 0x1d, 0x0a, 0x0a, 0x70, 0x69, 0x64, 0x73, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x09, 0x70, 0x69, 0x64, 0x73, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, - 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, - 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x3f, 0x0a, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, - 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, - 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, - 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x72, 0x74, - 0x52, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, - 0x6f, 0x72, 0x6d, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, - 0x6f, 0x72, 0x6d, 0x12, 0x50, 0x0a, 0x0b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, - 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, - 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, - 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x52, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, - 0x68, 0x65, 0x63, 0x6b, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x63, 0x6f, 0x6d, - 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, - 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x0b, 0x68, 0x65, - 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x22, 0xec, 0x01, 0x0a, 0x0a, 0x48, 0x6f, - 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2d, 0x0a, 0x12, 0x6d, 0x65, 0x6d, 0x6f, - 0x72, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x65, - 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x65, 0x6d, 0x6f, 0x72, - 0x79, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, - 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x70, - 0x75, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x0e, 0x63, 0x70, 0x75, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x70, 0x75, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x63, 0x70, 0x75, 0x4c, 0x69, 0x6d, 0x69, 0x74, - 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x70, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x74, 0x61, 0x72, - 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x75, 0x74, 0x6f, 0x5f, - 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x75, - 0x74, 0x6f, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x22, 0x57, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, - 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x69, 0x73, 0x61, 0x62, - 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x04, 0x74, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, - 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, - 0x6c, 0x22, 0x20, 0x0a, 0x0e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x02, 0x69, 0x64, 0x22, 0x5f, 0x0a, 0x0f, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, - 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x65, 0x72, 0x22, 0x35, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x22, 0x10, 0x0a, 0x0e, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x0a, - 0x0c, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x0f, 0x0a, - 0x0d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x37, - 0x0a, 0x0b, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, - 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, - 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x53, 0x74, 0x6f, 0x70, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x0a, 0x0b, 0x4b, 0x69, 0x6c, 0x6c, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x22, 0x0e, - 0x0a, 0x0c, 0x4b, 0x69, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xe4, - 0x04, 0x0a, 0x0a, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, - 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6d, - 0x61, 0x67, 0x65, 0x12, 0x3f, 0x0a, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x05, 0x70, - 0x6f, 0x72, 0x74, 0x73, 0x12, 0x53, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x04, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, - 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x75, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x6f, 0x6c, - 0x75, 0x6d, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x76, 0x6f, 0x6c, 0x75, - 0x6d, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x6c, 0x69, - 0x6d, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x65, 0x6d, 0x6f, 0x72, - 0x79, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x70, 0x75, 0x5f, 0x6c, 0x69, - 0x6d, 0x69, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x63, 0x70, 0x75, 0x4c, 0x69, - 0x6d, 0x69, 0x74, 0x12, 0x38, 0x0a, 0x18, 0x72, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x70, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x72, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, - 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, - 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x65, 0x6e, 0x76, 0x69, 0x72, - 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x6e, - 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x75, 0x74, - 0x6f, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, - 0x61, 0x75, 0x74, 0x6f, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x12, 0x52, 0x0a, 0x0b, 0x68, 0x65, - 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x30, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, - 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, - 0x6b, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x1a, - 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, - 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x0d, 0x0a, 0x0b, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x8c, 0x01, 0x0a, 0x0b, 0x45, 0x78, 0x65, 0x63, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x1b, - 0x0a, 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x61, - 0x72, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x61, 0x72, 0x67, 0x73, 0x12, - 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x65, 0x6e, - 0x76, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, - 0x74, 0x74, 0x79, 0x22, 0x26, 0x0a, 0x0c, 0x45, 0x78, 0x65, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x1f, 0x0a, 0x0b, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6c, - 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x61, 0x6c, 0x6c, 0x22, 0x5e, 0x0a, 0x0c, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0a, - 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x2e, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, - 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, - 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x22, 0x48, 0x0a, 0x0b, - 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, - 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, - 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x22, 0x24, 0x0a, 0x0c, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x32, 0xf2, 0x07, 0x0a, - 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x6b, 0x0a, 0x04, 0x4c, - 0x69, 0x73, 0x74, 0x12, 0x30, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, - 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, - 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, - 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6e, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, - 0x74, 0x12, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, - 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x04, 0x53, 0x74, 0x6f, 0x70, - 0x12, 0x30, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, - 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x04, 0x4b, 0x69, 0x6c, 0x6c, 0x12, 0x30, 0x2e, - 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x69, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, - 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x69, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x68, 0x0a, 0x03, 0x52, 0x75, 0x6e, 0x12, 0x2f, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, - 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x63, 0x6f, 0x6d, - 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x04, - 0x45, 0x78, 0x65, 0x63, 0x12, 0x30, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, - 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, - 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x65, - 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6d, 0x0a, 0x04, 0x4c, 0x6f, 0x67, - 0x73, 0x12, 0x30, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, - 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x71, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x12, 0x32, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, - 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x74, 0x0a, 0x07, 0x49, - 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x12, 0x33, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, - 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x73, - 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x63, 0x6f, - 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x76, - 0x31, 0x2e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x65, 0x2d, 0x63, - 0x6c, 0x69, 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2f, - 0x76, 0x31, 0x3b, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_cli_server_protos_containers_v1_containers_proto_rawDescOnce sync.Once - file_cli_server_protos_containers_v1_containers_proto_rawDescData = file_cli_server_protos_containers_v1_containers_proto_rawDesc -) - -func file_cli_server_protos_containers_v1_containers_proto_rawDescGZIP() []byte { - file_cli_server_protos_containers_v1_containers_proto_rawDescOnce.Do(func() { - file_cli_server_protos_containers_v1_containers_proto_rawDescData = protoimpl.X.CompressGZIP(file_cli_server_protos_containers_v1_containers_proto_rawDescData) - }) - return file_cli_server_protos_containers_v1_containers_proto_rawDescData -} - -var file_cli_server_protos_containers_v1_containers_proto_msgTypes = make([]protoimpl.MessageInfo, 23) -var file_cli_server_protos_containers_v1_containers_proto_goTypes = []interface{}{ - (*Port)(nil), // 0: com.docker.api.protos.containers.v1.Port - (*Container)(nil), // 1: com.docker.api.protos.containers.v1.Container - (*HostConfig)(nil), // 2: com.docker.api.protos.containers.v1.HostConfig - (*Healthcheck)(nil), // 3: com.docker.api.protos.containers.v1.Healthcheck - (*InspectRequest)(nil), // 4: com.docker.api.protos.containers.v1.InspectRequest - (*InspectResponse)(nil), // 5: com.docker.api.protos.containers.v1.InspectResponse - (*DeleteRequest)(nil), // 6: com.docker.api.protos.containers.v1.DeleteRequest - (*DeleteResponse)(nil), // 7: com.docker.api.protos.containers.v1.DeleteResponse - (*StartRequest)(nil), // 8: com.docker.api.protos.containers.v1.StartRequest - (*StartResponse)(nil), // 9: com.docker.api.protos.containers.v1.StartResponse - (*StopRequest)(nil), // 10: com.docker.api.protos.containers.v1.StopRequest - (*StopResponse)(nil), // 11: com.docker.api.protos.containers.v1.StopResponse - (*KillRequest)(nil), // 12: com.docker.api.protos.containers.v1.KillRequest - (*KillResponse)(nil), // 13: com.docker.api.protos.containers.v1.KillResponse - (*RunRequest)(nil), // 14: com.docker.api.protos.containers.v1.RunRequest - (*RunResponse)(nil), // 15: com.docker.api.protos.containers.v1.RunResponse - (*ExecRequest)(nil), // 16: com.docker.api.protos.containers.v1.ExecRequest - (*ExecResponse)(nil), // 17: com.docker.api.protos.containers.v1.ExecResponse - (*ListRequest)(nil), // 18: com.docker.api.protos.containers.v1.ListRequest - (*ListResponse)(nil), // 19: com.docker.api.protos.containers.v1.ListResponse - (*LogsRequest)(nil), // 20: com.docker.api.protos.containers.v1.LogsRequest - (*LogsResponse)(nil), // 21: com.docker.api.protos.containers.v1.LogsResponse - nil, // 22: com.docker.api.protos.containers.v1.RunRequest.LabelsEntry -} -var file_cli_server_protos_containers_v1_containers_proto_depIdxs = []int32{ - 0, // 0: com.docker.api.protos.containers.v1.Container.ports:type_name -> com.docker.api.protos.containers.v1.Port - 2, // 1: com.docker.api.protos.containers.v1.Container.host_config:type_name -> com.docker.api.protos.containers.v1.HostConfig - 3, // 2: com.docker.api.protos.containers.v1.Container.healthcheck:type_name -> com.docker.api.protos.containers.v1.Healthcheck - 1, // 3: com.docker.api.protos.containers.v1.InspectResponse.container:type_name -> com.docker.api.protos.containers.v1.Container - 0, // 4: com.docker.api.protos.containers.v1.RunRequest.ports:type_name -> com.docker.api.protos.containers.v1.Port - 22, // 5: com.docker.api.protos.containers.v1.RunRequest.labels:type_name -> com.docker.api.protos.containers.v1.RunRequest.LabelsEntry - 3, // 6: com.docker.api.protos.containers.v1.RunRequest.healthcheck:type_name -> com.docker.api.protos.containers.v1.Healthcheck - 1, // 7: com.docker.api.protos.containers.v1.ListResponse.containers:type_name -> com.docker.api.protos.containers.v1.Container - 18, // 8: com.docker.api.protos.containers.v1.Containers.List:input_type -> com.docker.api.protos.containers.v1.ListRequest - 8, // 9: com.docker.api.protos.containers.v1.Containers.Start:input_type -> com.docker.api.protos.containers.v1.StartRequest - 10, // 10: com.docker.api.protos.containers.v1.Containers.Stop:input_type -> com.docker.api.protos.containers.v1.StopRequest - 12, // 11: com.docker.api.protos.containers.v1.Containers.Kill:input_type -> com.docker.api.protos.containers.v1.KillRequest - 14, // 12: com.docker.api.protos.containers.v1.Containers.Run:input_type -> com.docker.api.protos.containers.v1.RunRequest - 16, // 13: com.docker.api.protos.containers.v1.Containers.Exec:input_type -> com.docker.api.protos.containers.v1.ExecRequest - 20, // 14: com.docker.api.protos.containers.v1.Containers.Logs:input_type -> com.docker.api.protos.containers.v1.LogsRequest - 6, // 15: com.docker.api.protos.containers.v1.Containers.Delete:input_type -> com.docker.api.protos.containers.v1.DeleteRequest - 4, // 16: com.docker.api.protos.containers.v1.Containers.Inspect:input_type -> com.docker.api.protos.containers.v1.InspectRequest - 19, // 17: com.docker.api.protos.containers.v1.Containers.List:output_type -> com.docker.api.protos.containers.v1.ListResponse - 9, // 18: com.docker.api.protos.containers.v1.Containers.Start:output_type -> com.docker.api.protos.containers.v1.StartResponse - 11, // 19: com.docker.api.protos.containers.v1.Containers.Stop:output_type -> com.docker.api.protos.containers.v1.StopResponse - 13, // 20: com.docker.api.protos.containers.v1.Containers.Kill:output_type -> com.docker.api.protos.containers.v1.KillResponse - 15, // 21: com.docker.api.protos.containers.v1.Containers.Run:output_type -> com.docker.api.protos.containers.v1.RunResponse - 17, // 22: com.docker.api.protos.containers.v1.Containers.Exec:output_type -> com.docker.api.protos.containers.v1.ExecResponse - 21, // 23: com.docker.api.protos.containers.v1.Containers.Logs:output_type -> com.docker.api.protos.containers.v1.LogsResponse - 7, // 24: com.docker.api.protos.containers.v1.Containers.Delete:output_type -> com.docker.api.protos.containers.v1.DeleteResponse - 5, // 25: com.docker.api.protos.containers.v1.Containers.Inspect:output_type -> com.docker.api.protos.containers.v1.InspectResponse - 17, // [17:26] is the sub-list for method output_type - 8, // [8:17] is the sub-list for method input_type - 8, // [8:8] is the sub-list for extension type_name - 8, // [8:8] is the sub-list for extension extendee - 0, // [0:8] is the sub-list for field type_name -} - -func init() { file_cli_server_protos_containers_v1_containers_proto_init() } -func file_cli_server_protos_containers_v1_containers_proto_init() { - if File_cli_server_protos_containers_v1_containers_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_cli_server_protos_containers_v1_containers_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Port); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_containers_v1_containers_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Container); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_containers_v1_containers_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HostConfig); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_containers_v1_containers_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Healthcheck); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_containers_v1_containers_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*InspectRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_containers_v1_containers_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*InspectResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_containers_v1_containers_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_containers_v1_containers_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_containers_v1_containers_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StartRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_containers_v1_containers_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StartResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_containers_v1_containers_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StopRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_containers_v1_containers_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StopResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_containers_v1_containers_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*KillRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_containers_v1_containers_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*KillResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_containers_v1_containers_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RunRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_containers_v1_containers_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RunResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_containers_v1_containers_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ExecRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_containers_v1_containers_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ExecResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_containers_v1_containers_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_containers_v1_containers_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_containers_v1_containers_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LogsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_containers_v1_containers_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LogsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_cli_server_protos_containers_v1_containers_proto_rawDesc, - NumEnums: 0, - NumMessages: 23, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_cli_server_protos_containers_v1_containers_proto_goTypes, - DependencyIndexes: file_cli_server_protos_containers_v1_containers_proto_depIdxs, - MessageInfos: file_cli_server_protos_containers_v1_containers_proto_msgTypes, - }.Build() - File_cli_server_protos_containers_v1_containers_proto = out.File - file_cli_server_protos_containers_v1_containers_proto_rawDesc = nil - file_cli_server_protos_containers_v1_containers_proto_goTypes = nil - file_cli_server_protos_containers_v1_containers_proto_depIdxs = nil -} - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConnInterface - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion6 - -// ContainersClient is the client API for Containers service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. -type ContainersClient interface { - List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) - Start(ctx context.Context, in *StartRequest, opts ...grpc.CallOption) (*StartResponse, error) - Stop(ctx context.Context, in *StopRequest, opts ...grpc.CallOption) (*StopResponse, error) - Kill(ctx context.Context, in *KillRequest, opts ...grpc.CallOption) (*KillResponse, error) - Run(ctx context.Context, in *RunRequest, opts ...grpc.CallOption) (*RunResponse, error) - Exec(ctx context.Context, in *ExecRequest, opts ...grpc.CallOption) (*ExecResponse, error) - Logs(ctx context.Context, in *LogsRequest, opts ...grpc.CallOption) (Containers_LogsClient, error) - Delete(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*DeleteResponse, error) - Inspect(ctx context.Context, in *InspectRequest, opts ...grpc.CallOption) (*InspectResponse, error) -} - -type containersClient struct { - cc grpc.ClientConnInterface -} - -func NewContainersClient(cc grpc.ClientConnInterface) ContainersClient { - return &containersClient{cc} -} - -func (c *containersClient) List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) { - out := new(ListResponse) - err := c.cc.Invoke(ctx, "/com.docker.api.protos.containers.v1.Containers/List", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *containersClient) Start(ctx context.Context, in *StartRequest, opts ...grpc.CallOption) (*StartResponse, error) { - out := new(StartResponse) - err := c.cc.Invoke(ctx, "/com.docker.api.protos.containers.v1.Containers/Start", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *containersClient) Stop(ctx context.Context, in *StopRequest, opts ...grpc.CallOption) (*StopResponse, error) { - out := new(StopResponse) - err := c.cc.Invoke(ctx, "/com.docker.api.protos.containers.v1.Containers/Stop", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *containersClient) Kill(ctx context.Context, in *KillRequest, opts ...grpc.CallOption) (*KillResponse, error) { - out := new(KillResponse) - err := c.cc.Invoke(ctx, "/com.docker.api.protos.containers.v1.Containers/Kill", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *containersClient) Run(ctx context.Context, in *RunRequest, opts ...grpc.CallOption) (*RunResponse, error) { - out := new(RunResponse) - err := c.cc.Invoke(ctx, "/com.docker.api.protos.containers.v1.Containers/Run", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *containersClient) Exec(ctx context.Context, in *ExecRequest, opts ...grpc.CallOption) (*ExecResponse, error) { - out := new(ExecResponse) - err := c.cc.Invoke(ctx, "/com.docker.api.protos.containers.v1.Containers/Exec", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *containersClient) Logs(ctx context.Context, in *LogsRequest, opts ...grpc.CallOption) (Containers_LogsClient, error) { - stream, err := c.cc.NewStream(ctx, &_Containers_serviceDesc.Streams[0], "/com.docker.api.protos.containers.v1.Containers/Logs", opts...) - if err != nil { - return nil, err - } - x := &containersLogsClient{stream} - if err := x.ClientStream.SendMsg(in); err != nil { - return nil, err - } - if err := x.ClientStream.CloseSend(); err != nil { - return nil, err - } - return x, nil -} - -type Containers_LogsClient interface { - Recv() (*LogsResponse, error) - grpc.ClientStream -} - -type containersLogsClient struct { - grpc.ClientStream -} - -func (x *containersLogsClient) Recv() (*LogsResponse, error) { - m := new(LogsResponse) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -func (c *containersClient) Delete(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*DeleteResponse, error) { - out := new(DeleteResponse) - err := c.cc.Invoke(ctx, "/com.docker.api.protos.containers.v1.Containers/Delete", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *containersClient) Inspect(ctx context.Context, in *InspectRequest, opts ...grpc.CallOption) (*InspectResponse, error) { - out := new(InspectResponse) - err := c.cc.Invoke(ctx, "/com.docker.api.protos.containers.v1.Containers/Inspect", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// ContainersServer is the server API for Containers service. -type ContainersServer interface { - List(context.Context, *ListRequest) (*ListResponse, error) - Start(context.Context, *StartRequest) (*StartResponse, error) - Stop(context.Context, *StopRequest) (*StopResponse, error) - Kill(context.Context, *KillRequest) (*KillResponse, error) - Run(context.Context, *RunRequest) (*RunResponse, error) - Exec(context.Context, *ExecRequest) (*ExecResponse, error) - Logs(*LogsRequest, Containers_LogsServer) error - Delete(context.Context, *DeleteRequest) (*DeleteResponse, error) - Inspect(context.Context, *InspectRequest) (*InspectResponse, error) -} - -// UnimplementedContainersServer can be embedded to have forward compatible implementations. -type UnimplementedContainersServer struct { -} - -func (*UnimplementedContainersServer) List(context.Context, *ListRequest) (*ListResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method List not implemented") -} -func (*UnimplementedContainersServer) Start(context.Context, *StartRequest) (*StartResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Start not implemented") -} -func (*UnimplementedContainersServer) Stop(context.Context, *StopRequest) (*StopResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Stop not implemented") -} -func (*UnimplementedContainersServer) Kill(context.Context, *KillRequest) (*KillResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Kill not implemented") -} -func (*UnimplementedContainersServer) Run(context.Context, *RunRequest) (*RunResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Run not implemented") -} -func (*UnimplementedContainersServer) Exec(context.Context, *ExecRequest) (*ExecResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Exec not implemented") -} -func (*UnimplementedContainersServer) Logs(*LogsRequest, Containers_LogsServer) error { - return status.Errorf(codes.Unimplemented, "method Logs not implemented") -} -func (*UnimplementedContainersServer) Delete(context.Context, *DeleteRequest) (*DeleteResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Delete not implemented") -} -func (*UnimplementedContainersServer) Inspect(context.Context, *InspectRequest) (*InspectResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Inspect not implemented") -} - -func RegisterContainersServer(s *grpc.Server, srv ContainersServer) { - s.RegisterService(&_Containers_serviceDesc, srv) -} - -func _Containers_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ListRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ContainersServer).List(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.docker.api.protos.containers.v1.Containers/List", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ContainersServer).List(ctx, req.(*ListRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Containers_Start_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(StartRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ContainersServer).Start(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.docker.api.protos.containers.v1.Containers/Start", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ContainersServer).Start(ctx, req.(*StartRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Containers_Stop_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(StopRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ContainersServer).Stop(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.docker.api.protos.containers.v1.Containers/Stop", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ContainersServer).Stop(ctx, req.(*StopRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Containers_Kill_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(KillRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ContainersServer).Kill(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.docker.api.protos.containers.v1.Containers/Kill", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ContainersServer).Kill(ctx, req.(*KillRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Containers_Run_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(RunRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ContainersServer).Run(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.docker.api.protos.containers.v1.Containers/Run", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ContainersServer).Run(ctx, req.(*RunRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Containers_Exec_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ExecRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ContainersServer).Exec(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.docker.api.protos.containers.v1.Containers/Exec", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ContainersServer).Exec(ctx, req.(*ExecRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Containers_Logs_Handler(srv interface{}, stream grpc.ServerStream) error { - m := new(LogsRequest) - if err := stream.RecvMsg(m); err != nil { - return err - } - return srv.(ContainersServer).Logs(m, &containersLogsServer{stream}) -} - -type Containers_LogsServer interface { - Send(*LogsResponse) error - grpc.ServerStream -} - -type containersLogsServer struct { - grpc.ServerStream -} - -func (x *containersLogsServer) Send(m *LogsResponse) error { - return x.ServerStream.SendMsg(m) -} - -func _Containers_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DeleteRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ContainersServer).Delete(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.docker.api.protos.containers.v1.Containers/Delete", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ContainersServer).Delete(ctx, req.(*DeleteRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Containers_Inspect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(InspectRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ContainersServer).Inspect(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.docker.api.protos.containers.v1.Containers/Inspect", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ContainersServer).Inspect(ctx, req.(*InspectRequest)) - } - return interceptor(ctx, in, info, handler) -} - -var _Containers_serviceDesc = grpc.ServiceDesc{ - ServiceName: "com.docker.api.protos.containers.v1.Containers", - HandlerType: (*ContainersServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "List", - Handler: _Containers_List_Handler, - }, - { - MethodName: "Start", - Handler: _Containers_Start_Handler, - }, - { - MethodName: "Stop", - Handler: _Containers_Stop_Handler, - }, - { - MethodName: "Kill", - Handler: _Containers_Kill_Handler, - }, - { - MethodName: "Run", - Handler: _Containers_Run_Handler, - }, - { - MethodName: "Exec", - Handler: _Containers_Exec_Handler, - }, - { - MethodName: "Delete", - Handler: _Containers_Delete_Handler, - }, - { - MethodName: "Inspect", - Handler: _Containers_Inspect_Handler, - }, - }, - Streams: []grpc.StreamDesc{ - { - StreamName: "Logs", - Handler: _Containers_Logs_Handler, - ServerStreams: true, - }, - }, - Metadata: "cli/server/protos/containers/v1/containers.proto", -} diff --git a/cli/server/protos/containers/v1/containers.proto b/cli/server/protos/containers/v1/containers.proto deleted file mode 100644 index 86ea30fb6..000000000 --- a/cli/server/protos/containers/v1/containers.proto +++ /dev/null @@ -1,158 +0,0 @@ -// -// Copyright 2020 Docker Compose CLI authors - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto3"; - -package com.docker.api.protos.containers.v1; - -option go_package = "github.com/docker/compose-cli/cli/server/protos/containers/v1;v1"; - -service Containers { - rpc List(ListRequest) returns (ListResponse); - rpc Start(StartRequest) returns (StartResponse); - rpc Stop(StopRequest) returns (StopResponse); - rpc Kill(KillRequest) returns (KillResponse); - rpc Run(RunRequest) returns (RunResponse); - rpc Exec(ExecRequest) returns (ExecResponse); - rpc Logs(LogsRequest) returns (stream LogsResponse); - rpc Delete(DeleteRequest) returns (DeleteResponse); - rpc Inspect(InspectRequest) returns (InspectResponse); -} - -message Port { - uint32 host_port = 1; - uint32 container_port = 2; - string protocol = 3; - string host_ip = 4; -} - -message Container { - string id = 1; - string image = 2; - string status = 3; - string command = 4; - uint64 cpu_time = 5; - uint64 memory_usage = 6; - uint64 pids_current = 8; - uint64 pids_limit = 9; - repeated string labels = 10; - repeated Port ports = 11; - string platform = 13; - HostConfig host_config = 15; - Healthcheck healthcheck = 16; -} - -message HostConfig { - uint64 memory_reservation = 1; - uint64 memory_limit = 2; - uint64 cpu_reservation = 3; - uint64 cpu_limit = 4; - string restart_policy = 5; - bool auto_remove = 6; -} - -message Healthcheck { - bool disable = 1; - repeated string test = 2; - int64 interval = 3; -} - -message InspectRequest { - string id = 1; -} - -message InspectResponse { - Container container = 1; -} - -message DeleteRequest { - string id = 1; - bool force = 2; -} - -message DeleteResponse { -} - -message StartRequest { - string id = 1; -} - -message StartResponse { -} - -message StopRequest { - string id = 1; - uint32 timeout = 2; -} - -message StopResponse { -} - -message KillRequest { - string id = 1; - string signal = 2; -} - -message KillResponse { -} - -message RunRequest { - string id = 1; - string image = 2; - repeated Port ports = 3; - map labels = 4; - repeated string volumes = 5; - uint64 memory_limit = 6; - uint64 cpu_limit = 7; - string restart_policy_condition = 8; - repeated string command = 9; - repeated string environment = 10; - bool auto_remove = 11; - Healthcheck healthcheck = 12; - string platform = 13; -} - -message RunResponse { -} - -message ExecRequest { - string id = 1; - string command = 2; - string stream_id = 3; - repeated string args = 4; - repeated string env = 5; - bool tty = 6; -} - -message ExecResponse { - bytes output = 1; -} - -message ListRequest { - bool all = 1; -} - -message ListResponse { - repeated Container containers = 1; -} - -message LogsRequest { - string container_id = 1; - bool follow = 3; -} - -message LogsResponse { - bytes value = 1; -} diff --git a/cli/server/protos/containers/v1/doc.go b/cli/server/protos/containers/v1/doc.go deleted file mode 100644 index b0664bbbf..000000000 --- a/cli/server/protos/containers/v1/doc.go +++ /dev/null @@ -1,17 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package v1 diff --git a/cli/server/protos/contexts/v1/contexts.pb.go b/cli/server/protos/contexts/v1/contexts.pb.go deleted file mode 100644 index 7153570c8..000000000 --- a/cli/server/protos/contexts/v1/contexts.pb.go +++ /dev/null @@ -1,873 +0,0 @@ -// -// Copyright 2020 Docker Compose CLI authors - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.25.0 -// protoc v3.13.0 -// source: cli/server/protos/contexts/v1/contexts.proto - -package v1 - -import ( - context "context" - proto "github.com/golang/protobuf/proto" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// This is a compile-time assertion that a sufficiently up-to-date version -// of the legacy proto package is being used. -const _ = proto.ProtoPackageIsVersion4 - -type Context struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - ContextType string `protobuf:"bytes,2,opt,name=contextType,proto3" json:"contextType,omitempty"` - Current bool `protobuf:"varint,3,opt,name=current,proto3" json:"current,omitempty"` - Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` - // Types that are assignable to Endpoint: - // *Context_DockerEndpoint - // *Context_AciEndpoint - // *Context_EcsEndpoint - Endpoint isContext_Endpoint `protobuf_oneof:"Endpoint"` -} - -func (x *Context) Reset() { - *x = Context{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Context) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Context) ProtoMessage() {} - -func (x *Context) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Context.ProtoReflect.Descriptor instead. -func (*Context) Descriptor() ([]byte, []int) { - return file_cli_server_protos_contexts_v1_contexts_proto_rawDescGZIP(), []int{0} -} - -func (x *Context) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *Context) GetContextType() string { - if x != nil { - return x.ContextType - } - return "" -} - -func (x *Context) GetCurrent() bool { - if x != nil { - return x.Current - } - return false -} - -func (x *Context) GetDescription() string { - if x != nil { - return x.Description - } - return "" -} - -func (m *Context) GetEndpoint() isContext_Endpoint { - if m != nil { - return m.Endpoint - } - return nil -} - -func (x *Context) GetDockerEndpoint() *DockerEndpoint { - if x, ok := x.GetEndpoint().(*Context_DockerEndpoint); ok { - return x.DockerEndpoint - } - return nil -} - -func (x *Context) GetAciEndpoint() *AciEndpoint { - if x, ok := x.GetEndpoint().(*Context_AciEndpoint); ok { - return x.AciEndpoint - } - return nil -} - -func (x *Context) GetEcsEndpoint() *EcsEndpoint { - if x, ok := x.GetEndpoint().(*Context_EcsEndpoint); ok { - return x.EcsEndpoint - } - return nil -} - -type isContext_Endpoint interface { - isContext_Endpoint() -} - -type Context_DockerEndpoint struct { - DockerEndpoint *DockerEndpoint `protobuf:"bytes,5,opt,name=docker_endpoint,json=dockerEndpoint,proto3,oneof"` -} - -type Context_AciEndpoint struct { - AciEndpoint *AciEndpoint `protobuf:"bytes,6,opt,name=aci_endpoint,json=aciEndpoint,proto3,oneof"` -} - -type Context_EcsEndpoint struct { - EcsEndpoint *EcsEndpoint `protobuf:"bytes,7,opt,name=ecs_endpoint,json=ecsEndpoint,proto3,oneof"` -} - -func (*Context_DockerEndpoint) isContext_Endpoint() {} - -func (*Context_AciEndpoint) isContext_Endpoint() {} - -func (*Context_EcsEndpoint) isContext_Endpoint() {} - -type DockerEndpoint struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` -} - -func (x *DockerEndpoint) Reset() { - *x = DockerEndpoint{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *DockerEndpoint) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DockerEndpoint) ProtoMessage() {} - -func (x *DockerEndpoint) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DockerEndpoint.ProtoReflect.Descriptor instead. -func (*DockerEndpoint) Descriptor() ([]byte, []int) { - return file_cli_server_protos_contexts_v1_contexts_proto_rawDescGZIP(), []int{1} -} - -func (x *DockerEndpoint) GetHost() string { - if x != nil { - return x.Host - } - return "" -} - -type AciEndpoint struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Region string `protobuf:"bytes,1,opt,name=region,proto3" json:"region,omitempty"` - ResourceGroup string `protobuf:"bytes,2,opt,name=resource_group,json=resourceGroup,proto3" json:"resource_group,omitempty"` - SubscriptionId string `protobuf:"bytes,3,opt,name=subscription_id,json=subscriptionId,proto3" json:"subscription_id,omitempty"` -} - -func (x *AciEndpoint) Reset() { - *x = AciEndpoint{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AciEndpoint) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AciEndpoint) ProtoMessage() {} - -func (x *AciEndpoint) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AciEndpoint.ProtoReflect.Descriptor instead. -func (*AciEndpoint) Descriptor() ([]byte, []int) { - return file_cli_server_protos_contexts_v1_contexts_proto_rawDescGZIP(), []int{2} -} - -func (x *AciEndpoint) GetRegion() string { - if x != nil { - return x.Region - } - return "" -} - -func (x *AciEndpoint) GetResourceGroup() string { - if x != nil { - return x.ResourceGroup - } - return "" -} - -func (x *AciEndpoint) GetSubscriptionId() string { - if x != nil { - return x.SubscriptionId - } - return "" -} - -type EcsEndpoint struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Profile string `protobuf:"bytes,1,opt,name=profile,proto3" json:"profile,omitempty"` - FromEnvironment bool `protobuf:"varint,2,opt,name=from_environment,json=fromEnvironment,proto3" json:"from_environment,omitempty"` -} - -func (x *EcsEndpoint) Reset() { - *x = EcsEndpoint{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *EcsEndpoint) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*EcsEndpoint) ProtoMessage() {} - -func (x *EcsEndpoint) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use EcsEndpoint.ProtoReflect.Descriptor instead. -func (*EcsEndpoint) Descriptor() ([]byte, []int) { - return file_cli_server_protos_contexts_v1_contexts_proto_rawDescGZIP(), []int{3} -} - -func (x *EcsEndpoint) GetProfile() string { - if x != nil { - return x.Profile - } - return "" -} - -func (x *EcsEndpoint) GetFromEnvironment() bool { - if x != nil { - return x.FromEnvironment - } - return false -} - -type SetCurrentRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` -} - -func (x *SetCurrentRequest) Reset() { - *x = SetCurrentRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SetCurrentRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SetCurrentRequest) ProtoMessage() {} - -func (x *SetCurrentRequest) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SetCurrentRequest.ProtoReflect.Descriptor instead. -func (*SetCurrentRequest) Descriptor() ([]byte, []int) { - return file_cli_server_protos_contexts_v1_contexts_proto_rawDescGZIP(), []int{4} -} - -func (x *SetCurrentRequest) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -type SetCurrentResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *SetCurrentResponse) Reset() { - *x = SetCurrentResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SetCurrentResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SetCurrentResponse) ProtoMessage() {} - -func (x *SetCurrentResponse) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SetCurrentResponse.ProtoReflect.Descriptor instead. -func (*SetCurrentResponse) Descriptor() ([]byte, []int) { - return file_cli_server_protos_contexts_v1_contexts_proto_rawDescGZIP(), []int{5} -} - -type ListRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *ListRequest) Reset() { - *x = ListRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ListRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListRequest) ProtoMessage() {} - -func (x *ListRequest) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListRequest.ProtoReflect.Descriptor instead. -func (*ListRequest) Descriptor() ([]byte, []int) { - return file_cli_server_protos_contexts_v1_contexts_proto_rawDescGZIP(), []int{6} -} - -type ListResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Contexts []*Context `protobuf:"bytes,1,rep,name=contexts,proto3" json:"contexts,omitempty"` -} - -func (x *ListResponse) Reset() { - *x = ListResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ListResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListResponse) ProtoMessage() {} - -func (x *ListResponse) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListResponse.ProtoReflect.Descriptor instead. -func (*ListResponse) Descriptor() ([]byte, []int) { - return file_cli_server_protos_contexts_v1_contexts_proto_rawDescGZIP(), []int{7} -} - -func (x *ListResponse) GetContexts() []*Context { - if x != nil { - return x.Contexts - } - return nil -} - -var File_cli_server_protos_contexts_v1_contexts_proto protoreflect.FileDescriptor - -var file_cli_server_protos_contexts_v1_contexts_proto_rawDesc = []byte{ - 0x0a, 0x2c, 0x63, 0x6c, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x73, 0x2f, 0x76, 0x31, 0x2f, - 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x20, - 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x76, 0x31, - 0x22, 0x8c, 0x03, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x12, 0x0a, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x54, 0x79, 0x70, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x07, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x0b, - 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x5b, - 0x0a, 0x0f, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, - 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, - 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x6f, 0x63, 0x6b, 0x65, - 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x64, 0x6f, 0x63, - 0x6b, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x52, 0x0a, 0x0c, 0x61, - 0x63, 0x69, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x69, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x48, 0x00, 0x52, 0x0b, 0x61, 0x63, 0x69, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, - 0x52, 0x0a, 0x0c, 0x65, 0x63, 0x73, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, - 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x63, 0x73, 0x45, 0x6e, 0x64, 0x70, - 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x65, 0x63, 0x73, 0x45, 0x6e, 0x64, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x42, 0x0a, 0x0a, 0x08, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, - 0x24, 0x0a, 0x0e, 0x44, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x68, 0x6f, 0x73, 0x74, 0x22, 0x75, 0x0a, 0x0b, 0x41, 0x63, 0x69, 0x45, 0x6e, 0x64, 0x70, - 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, - 0x6f, 0x75, 0x70, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x75, - 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x52, 0x0a, 0x0b, - 0x45, 0x63, 0x73, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, - 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, - 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x65, 0x6e, - 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x0f, 0x66, 0x72, 0x6f, 0x6d, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, - 0x22, 0x27, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x14, 0x0a, 0x12, 0x53, 0x65, 0x74, - 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x0d, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x55, - 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, - 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x29, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x08, 0x63, 0x6f, 0x6e, - 0x74, 0x65, 0x78, 0x74, 0x73, 0x32, 0xea, 0x01, 0x0a, 0x08, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, - 0x74, 0x73, 0x12, 0x77, 0x0a, 0x0a, 0x53, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, - 0x12, 0x33, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, - 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x04, 0x4c, - 0x69, 0x73, 0x74, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, - 0x65, 0x78, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, - 0x78, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x42, 0x3f, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x65, 0x2d, - 0x63, 0x6c, 0x69, 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x76, 0x31, - 0x3b, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_cli_server_protos_contexts_v1_contexts_proto_rawDescOnce sync.Once - file_cli_server_protos_contexts_v1_contexts_proto_rawDescData = file_cli_server_protos_contexts_v1_contexts_proto_rawDesc -) - -func file_cli_server_protos_contexts_v1_contexts_proto_rawDescGZIP() []byte { - file_cli_server_protos_contexts_v1_contexts_proto_rawDescOnce.Do(func() { - file_cli_server_protos_contexts_v1_contexts_proto_rawDescData = protoimpl.X.CompressGZIP(file_cli_server_protos_contexts_v1_contexts_proto_rawDescData) - }) - return file_cli_server_protos_contexts_v1_contexts_proto_rawDescData -} - -var file_cli_server_protos_contexts_v1_contexts_proto_msgTypes = make([]protoimpl.MessageInfo, 8) -var file_cli_server_protos_contexts_v1_contexts_proto_goTypes = []interface{}{ - (*Context)(nil), // 0: com.docker.api.protos.context.v1.Context - (*DockerEndpoint)(nil), // 1: com.docker.api.protos.context.v1.DockerEndpoint - (*AciEndpoint)(nil), // 2: com.docker.api.protos.context.v1.AciEndpoint - (*EcsEndpoint)(nil), // 3: com.docker.api.protos.context.v1.EcsEndpoint - (*SetCurrentRequest)(nil), // 4: com.docker.api.protos.context.v1.SetCurrentRequest - (*SetCurrentResponse)(nil), // 5: com.docker.api.protos.context.v1.SetCurrentResponse - (*ListRequest)(nil), // 6: com.docker.api.protos.context.v1.ListRequest - (*ListResponse)(nil), // 7: com.docker.api.protos.context.v1.ListResponse -} -var file_cli_server_protos_contexts_v1_contexts_proto_depIdxs = []int32{ - 1, // 0: com.docker.api.protos.context.v1.Context.docker_endpoint:type_name -> com.docker.api.protos.context.v1.DockerEndpoint - 2, // 1: com.docker.api.protos.context.v1.Context.aci_endpoint:type_name -> com.docker.api.protos.context.v1.AciEndpoint - 3, // 2: com.docker.api.protos.context.v1.Context.ecs_endpoint:type_name -> com.docker.api.protos.context.v1.EcsEndpoint - 0, // 3: com.docker.api.protos.context.v1.ListResponse.contexts:type_name -> com.docker.api.protos.context.v1.Context - 4, // 4: com.docker.api.protos.context.v1.Contexts.SetCurrent:input_type -> com.docker.api.protos.context.v1.SetCurrentRequest - 6, // 5: com.docker.api.protos.context.v1.Contexts.List:input_type -> com.docker.api.protos.context.v1.ListRequest - 5, // 6: com.docker.api.protos.context.v1.Contexts.SetCurrent:output_type -> com.docker.api.protos.context.v1.SetCurrentResponse - 7, // 7: com.docker.api.protos.context.v1.Contexts.List:output_type -> com.docker.api.protos.context.v1.ListResponse - 6, // [6:8] is the sub-list for method output_type - 4, // [4:6] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name -} - -func init() { file_cli_server_protos_contexts_v1_contexts_proto_init() } -func file_cli_server_protos_contexts_v1_contexts_proto_init() { - if File_cli_server_protos_contexts_v1_contexts_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Context); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DockerEndpoint); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AciEndpoint); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*EcsEndpoint); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SetCurrentRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SetCurrentResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_cli_server_protos_contexts_v1_contexts_proto_msgTypes[0].OneofWrappers = []interface{}{ - (*Context_DockerEndpoint)(nil), - (*Context_AciEndpoint)(nil), - (*Context_EcsEndpoint)(nil), - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_cli_server_protos_contexts_v1_contexts_proto_rawDesc, - NumEnums: 0, - NumMessages: 8, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_cli_server_protos_contexts_v1_contexts_proto_goTypes, - DependencyIndexes: file_cli_server_protos_contexts_v1_contexts_proto_depIdxs, - MessageInfos: file_cli_server_protos_contexts_v1_contexts_proto_msgTypes, - }.Build() - File_cli_server_protos_contexts_v1_contexts_proto = out.File - file_cli_server_protos_contexts_v1_contexts_proto_rawDesc = nil - file_cli_server_protos_contexts_v1_contexts_proto_goTypes = nil - file_cli_server_protos_contexts_v1_contexts_proto_depIdxs = nil -} - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConnInterface - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion6 - -// ContextsClient is the client API for Contexts service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. -type ContextsClient interface { - // Sets the current request for all calls - SetCurrent(ctx context.Context, in *SetCurrentRequest, opts ...grpc.CallOption) (*SetCurrentResponse, error) - // Returns the list of existing contexts - List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) -} - -type contextsClient struct { - cc grpc.ClientConnInterface -} - -func NewContextsClient(cc grpc.ClientConnInterface) ContextsClient { - return &contextsClient{cc} -} - -func (c *contextsClient) SetCurrent(ctx context.Context, in *SetCurrentRequest, opts ...grpc.CallOption) (*SetCurrentResponse, error) { - out := new(SetCurrentResponse) - err := c.cc.Invoke(ctx, "/com.docker.api.protos.context.v1.Contexts/SetCurrent", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *contextsClient) List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) { - out := new(ListResponse) - err := c.cc.Invoke(ctx, "/com.docker.api.protos.context.v1.Contexts/List", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// ContextsServer is the server API for Contexts service. -type ContextsServer interface { - // Sets the current request for all calls - SetCurrent(context.Context, *SetCurrentRequest) (*SetCurrentResponse, error) - // Returns the list of existing contexts - List(context.Context, *ListRequest) (*ListResponse, error) -} - -// UnimplementedContextsServer can be embedded to have forward compatible implementations. -type UnimplementedContextsServer struct { -} - -func (*UnimplementedContextsServer) SetCurrent(context.Context, *SetCurrentRequest) (*SetCurrentResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method SetCurrent not implemented") -} -func (*UnimplementedContextsServer) List(context.Context, *ListRequest) (*ListResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method List not implemented") -} - -func RegisterContextsServer(s *grpc.Server, srv ContextsServer) { - s.RegisterService(&_Contexts_serviceDesc, srv) -} - -func _Contexts_SetCurrent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(SetCurrentRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ContextsServer).SetCurrent(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.docker.api.protos.context.v1.Contexts/SetCurrent", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ContextsServer).SetCurrent(ctx, req.(*SetCurrentRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Contexts_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ListRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ContextsServer).List(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.docker.api.protos.context.v1.Contexts/List", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ContextsServer).List(ctx, req.(*ListRequest)) - } - return interceptor(ctx, in, info, handler) -} - -var _Contexts_serviceDesc = grpc.ServiceDesc{ - ServiceName: "com.docker.api.protos.context.v1.Contexts", - HandlerType: (*ContextsServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "SetCurrent", - Handler: _Contexts_SetCurrent_Handler, - }, - { - MethodName: "List", - Handler: _Contexts_List_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "cli/server/protos/contexts/v1/contexts.proto", -} diff --git a/cli/server/protos/contexts/v1/contexts.proto b/cli/server/protos/contexts/v1/contexts.proto deleted file mode 100644 index a77239cac..000000000 --- a/cli/server/protos/contexts/v1/contexts.proto +++ /dev/null @@ -1,68 +0,0 @@ -// -// Copyright 2020 Docker Compose CLI authors - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto3"; - -package com.docker.api.protos.context.v1; - -option go_package = "github.com/docker/compose-cli/cli/server/protos/context/v1;v1"; - -service Contexts { - // Sets the current request for all calls - rpc SetCurrent(SetCurrentRequest) returns (SetCurrentResponse); - // Returns the list of existing contexts - rpc List(ListRequest) returns (ListResponse); -} - -message Context { - string name = 1; - string contextType = 2; - bool current = 3; - string description = 4; - oneof Endpoint { - DockerEndpoint docker_endpoint = 5; - AciEndpoint aci_endpoint = 6; - EcsEndpoint ecs_endpoint = 7; - } -} - -message DockerEndpoint { - string host = 1; -} - -message AciEndpoint { - string region = 1; - string resource_group = 2; - string subscription_id = 3; -} - -message EcsEndpoint { - string profile = 1; - bool from_environment = 2; -} - -message SetCurrentRequest { - string name = 1; -} - -message SetCurrentResponse { -} - -message ListRequest { -} - -message ListResponse { - repeated Context contexts = 1; -} diff --git a/cli/server/protos/contexts/v1/doc.go b/cli/server/protos/contexts/v1/doc.go deleted file mode 100644 index b0664bbbf..000000000 --- a/cli/server/protos/contexts/v1/doc.go +++ /dev/null @@ -1,17 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package v1 diff --git a/cli/server/protos/streams/v1/streams.pb.go b/cli/server/protos/streams/v1/streams.pb.go deleted file mode 100644 index bfc775cc8..000000000 --- a/cli/server/protos/streams/v1/streams.pb.go +++ /dev/null @@ -1,497 +0,0 @@ -// -// Copyright 2020 Docker Compose CLI authors - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.25.0 -// protoc v3.13.0 -// source: cli/server/protos/streams/v1/streams.proto - -package v1 - -import ( - context "context" - proto "github.com/golang/protobuf/proto" - any "github.com/golang/protobuf/ptypes/any" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// This is a compile-time assertion that a sufficiently up-to-date version -// of the legacy proto package is being used. -const _ = proto.ProtoPackageIsVersion4 - -type IOStream int32 - -const ( - IOStream_STDIN IOStream = 0 - IOStream_STDOUT IOStream = 1 - IOStream_STDERR IOStream = 2 -) - -// Enum value maps for IOStream. -var ( - IOStream_name = map[int32]string{ - 0: "STDIN", - 1: "STDOUT", - 2: "STDERR", - } - IOStream_value = map[string]int32{ - "STDIN": 0, - "STDOUT": 1, - "STDERR": 2, - } -) - -func (x IOStream) Enum() *IOStream { - p := new(IOStream) - *p = x - return p -} - -func (x IOStream) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (IOStream) Descriptor() protoreflect.EnumDescriptor { - return file_cli_server_protos_streams_v1_streams_proto_enumTypes[0].Descriptor() -} - -func (IOStream) Type() protoreflect.EnumType { - return &file_cli_server_protos_streams_v1_streams_proto_enumTypes[0] -} - -func (x IOStream) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use IOStream.Descriptor instead. -func (IOStream) EnumDescriptor() ([]byte, []int) { - return file_cli_server_protos_streams_v1_streams_proto_rawDescGZIP(), []int{0} -} - -type BytesMessage struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Type IOStream `protobuf:"varint,1,opt,name=type,proto3,enum=com.docker.api.protos.streams.v1.IOStream" json:"type,omitempty"` - Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` -} - -func (x *BytesMessage) Reset() { - *x = BytesMessage{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_streams_v1_streams_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *BytesMessage) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*BytesMessage) ProtoMessage() {} - -func (x *BytesMessage) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_streams_v1_streams_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use BytesMessage.ProtoReflect.Descriptor instead. -func (*BytesMessage) Descriptor() ([]byte, []int) { - return file_cli_server_protos_streams_v1_streams_proto_rawDescGZIP(), []int{0} -} - -func (x *BytesMessage) GetType() IOStream { - if x != nil { - return x.Type - } - return IOStream_STDIN -} - -func (x *BytesMessage) GetValue() []byte { - if x != nil { - return x.Value - } - return nil -} - -type ResizeMessage struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Width uint32 `protobuf:"varint,1,opt,name=width,proto3" json:"width,omitempty"` - Height uint32 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` -} - -func (x *ResizeMessage) Reset() { - *x = ResizeMessage{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_streams_v1_streams_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ResizeMessage) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ResizeMessage) ProtoMessage() {} - -func (x *ResizeMessage) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_streams_v1_streams_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ResizeMessage.ProtoReflect.Descriptor instead. -func (*ResizeMessage) Descriptor() ([]byte, []int) { - return file_cli_server_protos_streams_v1_streams_proto_rawDescGZIP(), []int{1} -} - -func (x *ResizeMessage) GetWidth() uint32 { - if x != nil { - return x.Width - } - return 0 -} - -func (x *ResizeMessage) GetHeight() uint32 { - if x != nil { - return x.Height - } - return 0 -} - -type ExitMessage struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Status uint32 `protobuf:"varint,1,opt,name=status,proto3" json:"status,omitempty"` -} - -func (x *ExitMessage) Reset() { - *x = ExitMessage{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_streams_v1_streams_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ExitMessage) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ExitMessage) ProtoMessage() {} - -func (x *ExitMessage) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_streams_v1_streams_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ExitMessage.ProtoReflect.Descriptor instead. -func (*ExitMessage) Descriptor() ([]byte, []int) { - return file_cli_server_protos_streams_v1_streams_proto_rawDescGZIP(), []int{2} -} - -func (x *ExitMessage) GetStatus() uint32 { - if x != nil { - return x.Status - } - return 0 -} - -var File_cli_server_protos_streams_v1_streams_proto protoreflect.FileDescriptor - -var file_cli_server_protos_streams_v1_streams_proto_rawDesc = []byte{ - 0x0a, 0x2a, 0x63, 0x6c, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x73, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x73, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x20, 0x63, 0x6f, - 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x73, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x1a, 0x19, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, - 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x64, 0x0a, 0x0c, 0x42, 0x79, 0x74, - 0x65, 0x73, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x3e, 0x0a, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, - 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, - 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x4f, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, - 0x3d, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x69, 0x7a, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x12, 0x14, 0x0a, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x25, - 0x0a, 0x0b, 0x45, 0x78, 0x69, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x2a, 0x2d, 0x0a, 0x08, 0x49, 0x4f, 0x53, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x44, 0x49, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, - 0x53, 0x54, 0x44, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x44, 0x45, - 0x52, 0x52, 0x10, 0x02, 0x32, 0x48, 0x0a, 0x09, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, - 0x67, 0x12, 0x3b, 0x0a, 0x09, 0x4e, 0x65, 0x77, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x14, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x41, 0x6e, 0x79, 0x1a, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x28, 0x01, 0x30, 0x01, 0x42, 0x3f, - 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x6f, 0x63, - 0x6b, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x65, 0x2d, 0x63, 0x6c, 0x69, 0x2f, - 0x63, 0x6c, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x73, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x76, 0x31, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_cli_server_protos_streams_v1_streams_proto_rawDescOnce sync.Once - file_cli_server_protos_streams_v1_streams_proto_rawDescData = file_cli_server_protos_streams_v1_streams_proto_rawDesc -) - -func file_cli_server_protos_streams_v1_streams_proto_rawDescGZIP() []byte { - file_cli_server_protos_streams_v1_streams_proto_rawDescOnce.Do(func() { - file_cli_server_protos_streams_v1_streams_proto_rawDescData = protoimpl.X.CompressGZIP(file_cli_server_protos_streams_v1_streams_proto_rawDescData) - }) - return file_cli_server_protos_streams_v1_streams_proto_rawDescData -} - -var file_cli_server_protos_streams_v1_streams_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_cli_server_protos_streams_v1_streams_proto_msgTypes = make([]protoimpl.MessageInfo, 3) -var file_cli_server_protos_streams_v1_streams_proto_goTypes = []interface{}{ - (IOStream)(0), // 0: com.docker.api.protos.streams.v1.IOStream - (*BytesMessage)(nil), // 1: com.docker.api.protos.streams.v1.BytesMessage - (*ResizeMessage)(nil), // 2: com.docker.api.protos.streams.v1.ResizeMessage - (*ExitMessage)(nil), // 3: com.docker.api.protos.streams.v1.ExitMessage - (*any.Any)(nil), // 4: google.protobuf.Any -} -var file_cli_server_protos_streams_v1_streams_proto_depIdxs = []int32{ - 0, // 0: com.docker.api.protos.streams.v1.BytesMessage.type:type_name -> com.docker.api.protos.streams.v1.IOStream - 4, // 1: com.docker.api.protos.streams.v1.Streaming.NewStream:input_type -> google.protobuf.Any - 4, // 2: com.docker.api.protos.streams.v1.Streaming.NewStream:output_type -> google.protobuf.Any - 2, // [2:3] is the sub-list for method output_type - 1, // [1:2] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name -} - -func init() { file_cli_server_protos_streams_v1_streams_proto_init() } -func file_cli_server_protos_streams_v1_streams_proto_init() { - if File_cli_server_protos_streams_v1_streams_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_cli_server_protos_streams_v1_streams_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BytesMessage); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_streams_v1_streams_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ResizeMessage); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_streams_v1_streams_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ExitMessage); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_cli_server_protos_streams_v1_streams_proto_rawDesc, - NumEnums: 1, - NumMessages: 3, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_cli_server_protos_streams_v1_streams_proto_goTypes, - DependencyIndexes: file_cli_server_protos_streams_v1_streams_proto_depIdxs, - EnumInfos: file_cli_server_protos_streams_v1_streams_proto_enumTypes, - MessageInfos: file_cli_server_protos_streams_v1_streams_proto_msgTypes, - }.Build() - File_cli_server_protos_streams_v1_streams_proto = out.File - file_cli_server_protos_streams_v1_streams_proto_rawDesc = nil - file_cli_server_protos_streams_v1_streams_proto_goTypes = nil - file_cli_server_protos_streams_v1_streams_proto_depIdxs = nil -} - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConnInterface - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion6 - -// StreamingClient is the client API for Streaming service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. -type StreamingClient interface { - NewStream(ctx context.Context, opts ...grpc.CallOption) (Streaming_NewStreamClient, error) -} - -type streamingClient struct { - cc grpc.ClientConnInterface -} - -func NewStreamingClient(cc grpc.ClientConnInterface) StreamingClient { - return &streamingClient{cc} -} - -func (c *streamingClient) NewStream(ctx context.Context, opts ...grpc.CallOption) (Streaming_NewStreamClient, error) { - stream, err := c.cc.NewStream(ctx, &_Streaming_serviceDesc.Streams[0], "/com.docker.api.protos.streams.v1.Streaming/NewStream", opts...) - if err != nil { - return nil, err - } - x := &streamingNewStreamClient{stream} - return x, nil -} - -type Streaming_NewStreamClient interface { - Send(*any.Any) error - Recv() (*any.Any, error) - grpc.ClientStream -} - -type streamingNewStreamClient struct { - grpc.ClientStream -} - -func (x *streamingNewStreamClient) Send(m *any.Any) error { - return x.ClientStream.SendMsg(m) -} - -func (x *streamingNewStreamClient) Recv() (*any.Any, error) { - m := new(any.Any) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -// StreamingServer is the server API for Streaming service. -type StreamingServer interface { - NewStream(Streaming_NewStreamServer) error -} - -// UnimplementedStreamingServer can be embedded to have forward compatible implementations. -type UnimplementedStreamingServer struct { -} - -func (*UnimplementedStreamingServer) NewStream(Streaming_NewStreamServer) error { - return status.Errorf(codes.Unimplemented, "method NewStream not implemented") -} - -func RegisterStreamingServer(s *grpc.Server, srv StreamingServer) { - s.RegisterService(&_Streaming_serviceDesc, srv) -} - -func _Streaming_NewStream_Handler(srv interface{}, stream grpc.ServerStream) error { - return srv.(StreamingServer).NewStream(&streamingNewStreamServer{stream}) -} - -type Streaming_NewStreamServer interface { - Send(*any.Any) error - Recv() (*any.Any, error) - grpc.ServerStream -} - -type streamingNewStreamServer struct { - grpc.ServerStream -} - -func (x *streamingNewStreamServer) Send(m *any.Any) error { - return x.ServerStream.SendMsg(m) -} - -func (x *streamingNewStreamServer) Recv() (*any.Any, error) { - m := new(any.Any) - if err := x.ServerStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -var _Streaming_serviceDesc = grpc.ServiceDesc{ - ServiceName: "com.docker.api.protos.streams.v1.Streaming", - HandlerType: (*StreamingServer)(nil), - Methods: []grpc.MethodDesc{}, - Streams: []grpc.StreamDesc{ - { - StreamName: "NewStream", - Handler: _Streaming_NewStream_Handler, - ServerStreams: true, - ClientStreams: true, - }, - }, - Metadata: "cli/server/protos/streams/v1/streams.proto", -} diff --git a/cli/server/protos/streams/v1/streams.proto b/cli/server/protos/streams/v1/streams.proto deleted file mode 100644 index cde5b3d3c..000000000 --- a/cli/server/protos/streams/v1/streams.proto +++ /dev/null @@ -1,46 +0,0 @@ -// -// Copyright 2020 Docker Compose CLI authors - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto3"; - -package com.docker.api.protos.streams.v1; - -import "google/protobuf/any.proto"; - -option go_package = "github.com/docker/compose-cli/cli/server/protos/streams/v1;v1"; - -service Streaming { - rpc NewStream(stream google.protobuf.Any) returns (stream google.protobuf.Any); -} - -enum IOStream { - STDIN = 0; - STDOUT = 1; - STDERR = 2; -} - -message BytesMessage { - IOStream type = 1; - bytes value = 2; -} - -message ResizeMessage { - uint32 width = 1; - uint32 height = 2; -} - -message ExitMessage { - uint32 status = 1; -} diff --git a/cli/server/protos/volumes/v1/volumes.pb.go b/cli/server/protos/volumes/v1/volumes.pb.go deleted file mode 100644 index 22e15063e..000000000 --- a/cli/server/protos/volumes/v1/volumes.pb.go +++ /dev/null @@ -1,1008 +0,0 @@ -// -// Copyright 2020 Docker Compose CLI authors - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.25.0 -// protoc v3.13.0 -// source: cli/server/protos/volumes/v1/volumes.proto - -package v1 - -import ( - context "context" - proto "github.com/golang/protobuf/proto" - _ "github.com/golang/protobuf/ptypes/any" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// This is a compile-time assertion that a sufficiently up-to-date version -// of the legacy proto package is being used. -const _ = proto.ProtoPackageIsVersion4 - -type Volume struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` -} - -func (x *Volume) Reset() { - *x = Volume{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Volume) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Volume) ProtoMessage() {} - -func (x *Volume) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Volume.ProtoReflect.Descriptor instead. -func (*Volume) Descriptor() ([]byte, []int) { - return file_cli_server_protos_volumes_v1_volumes_proto_rawDescGZIP(), []int{0} -} - -func (x *Volume) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *Volume) GetDescription() string { - if x != nil { - return x.Description - } - return "" -} - -type AciVolumeCreateOptions struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - StorageAccount string `protobuf:"bytes,1,opt,name=storage_account,json=storageAccount,proto3" json:"storage_account,omitempty"` -} - -func (x *AciVolumeCreateOptions) Reset() { - *x = AciVolumeCreateOptions{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AciVolumeCreateOptions) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AciVolumeCreateOptions) ProtoMessage() {} - -func (x *AciVolumeCreateOptions) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AciVolumeCreateOptions.ProtoReflect.Descriptor instead. -func (*AciVolumeCreateOptions) Descriptor() ([]byte, []int) { - return file_cli_server_protos_volumes_v1_volumes_proto_rawDescGZIP(), []int{1} -} - -func (x *AciVolumeCreateOptions) GetStorageAccount() string { - if x != nil { - return x.StorageAccount - } - return "" -} - -type VolumesCreateRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // Types that are assignable to Options: - // *VolumesCreateRequest_AciOption - Options isVolumesCreateRequest_Options `protobuf_oneof:"options"` -} - -func (x *VolumesCreateRequest) Reset() { - *x = VolumesCreateRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *VolumesCreateRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*VolumesCreateRequest) ProtoMessage() {} - -func (x *VolumesCreateRequest) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use VolumesCreateRequest.ProtoReflect.Descriptor instead. -func (*VolumesCreateRequest) Descriptor() ([]byte, []int) { - return file_cli_server_protos_volumes_v1_volumes_proto_rawDescGZIP(), []int{2} -} - -func (x *VolumesCreateRequest) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (m *VolumesCreateRequest) GetOptions() isVolumesCreateRequest_Options { - if m != nil { - return m.Options - } - return nil -} - -func (x *VolumesCreateRequest) GetAciOption() *AciVolumeCreateOptions { - if x, ok := x.GetOptions().(*VolumesCreateRequest_AciOption); ok { - return x.AciOption - } - return nil -} - -type isVolumesCreateRequest_Options interface { - isVolumesCreateRequest_Options() -} - -type VolumesCreateRequest_AciOption struct { - AciOption *AciVolumeCreateOptions `protobuf:"bytes,2,opt,name=aci_option,json=aciOption,proto3,oneof"` -} - -func (*VolumesCreateRequest_AciOption) isVolumesCreateRequest_Options() {} - -type VolumesCreateResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Volume *Volume `protobuf:"bytes,1,opt,name=volume,proto3" json:"volume,omitempty"` -} - -func (x *VolumesCreateResponse) Reset() { - *x = VolumesCreateResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *VolumesCreateResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*VolumesCreateResponse) ProtoMessage() {} - -func (x *VolumesCreateResponse) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use VolumesCreateResponse.ProtoReflect.Descriptor instead. -func (*VolumesCreateResponse) Descriptor() ([]byte, []int) { - return file_cli_server_protos_volumes_v1_volumes_proto_rawDescGZIP(), []int{3} -} - -func (x *VolumesCreateResponse) GetVolume() *Volume { - if x != nil { - return x.Volume - } - return nil -} - -type VolumesListRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *VolumesListRequest) Reset() { - *x = VolumesListRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *VolumesListRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*VolumesListRequest) ProtoMessage() {} - -func (x *VolumesListRequest) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use VolumesListRequest.ProtoReflect.Descriptor instead. -func (*VolumesListRequest) Descriptor() ([]byte, []int) { - return file_cli_server_protos_volumes_v1_volumes_proto_rawDescGZIP(), []int{4} -} - -type VolumesListResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Volumes []*Volume `protobuf:"bytes,1,rep,name=volumes,proto3" json:"volumes,omitempty"` -} - -func (x *VolumesListResponse) Reset() { - *x = VolumesListResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *VolumesListResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*VolumesListResponse) ProtoMessage() {} - -func (x *VolumesListResponse) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use VolumesListResponse.ProtoReflect.Descriptor instead. -func (*VolumesListResponse) Descriptor() ([]byte, []int) { - return file_cli_server_protos_volumes_v1_volumes_proto_rawDescGZIP(), []int{5} -} - -func (x *VolumesListResponse) GetVolumes() []*Volume { - if x != nil { - return x.Volumes - } - return nil -} - -type VolumesDeleteRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` -} - -func (x *VolumesDeleteRequest) Reset() { - *x = VolumesDeleteRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *VolumesDeleteRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*VolumesDeleteRequest) ProtoMessage() {} - -func (x *VolumesDeleteRequest) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use VolumesDeleteRequest.ProtoReflect.Descriptor instead. -func (*VolumesDeleteRequest) Descriptor() ([]byte, []int) { - return file_cli_server_protos_volumes_v1_volumes_proto_rawDescGZIP(), []int{6} -} - -func (x *VolumesDeleteRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -type VolumesDeleteResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *VolumesDeleteResponse) Reset() { - *x = VolumesDeleteResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *VolumesDeleteResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*VolumesDeleteResponse) ProtoMessage() {} - -func (x *VolumesDeleteResponse) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use VolumesDeleteResponse.ProtoReflect.Descriptor instead. -func (*VolumesDeleteResponse) Descriptor() ([]byte, []int) { - return file_cli_server_protos_volumes_v1_volumes_proto_rawDescGZIP(), []int{7} -} - -type VolumesInspectRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` -} - -func (x *VolumesInspectRequest) Reset() { - *x = VolumesInspectRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *VolumesInspectRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*VolumesInspectRequest) ProtoMessage() {} - -func (x *VolumesInspectRequest) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use VolumesInspectRequest.ProtoReflect.Descriptor instead. -func (*VolumesInspectRequest) Descriptor() ([]byte, []int) { - return file_cli_server_protos_volumes_v1_volumes_proto_rawDescGZIP(), []int{8} -} - -func (x *VolumesInspectRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -type VolumesInspectResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Volume *Volume `protobuf:"bytes,1,opt,name=volume,proto3" json:"volume,omitempty"` -} - -func (x *VolumesInspectResponse) Reset() { - *x = VolumesInspectResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *VolumesInspectResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*VolumesInspectResponse) ProtoMessage() {} - -func (x *VolumesInspectResponse) ProtoReflect() protoreflect.Message { - mi := &file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use VolumesInspectResponse.ProtoReflect.Descriptor instead. -func (*VolumesInspectResponse) Descriptor() ([]byte, []int) { - return file_cli_server_protos_volumes_v1_volumes_proto_rawDescGZIP(), []int{9} -} - -func (x *VolumesInspectResponse) GetVolume() *Volume { - if x != nil { - return x.Volume - } - return nil -} - -var File_cli_server_protos_volumes_v1_volumes_proto protoreflect.FileDescriptor - -var file_cli_server_protos_volumes_v1_volumes_proto_rawDesc = []byte{ - 0x0a, 0x2a, 0x63, 0x6c, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x73, 0x2f, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x76, - 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x20, 0x63, 0x6f, - 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x73, 0x2e, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x1a, 0x19, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, - 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x3a, 0x0a, 0x06, 0x56, 0x6f, 0x6c, - 0x75, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x02, 0x69, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x41, 0x0a, 0x16, 0x41, 0x63, 0x69, 0x56, 0x6f, 0x6c, 0x75, - 0x6d, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, - 0x27, 0x0a, 0x0f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, - 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x90, 0x01, 0x0a, 0x14, 0x56, 0x6f, 0x6c, - 0x75, 0x6d, 0x65, 0x73, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x59, 0x0a, 0x0a, 0x61, 0x63, 0x69, 0x5f, 0x6f, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, - 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x73, 0x2e, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x69, - 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x48, 0x00, 0x52, 0x09, 0x61, 0x63, 0x69, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x42, 0x09, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x59, 0x0a, 0x15, 0x56, - 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x06, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, - 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x76, 0x6f, 0x6c, - 0x75, 0x6d, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x06, - 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x22, 0x14, 0x0a, 0x12, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, - 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x59, 0x0a, 0x13, - 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, - 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x76, 0x6f, 0x6c, - 0x75, 0x6d, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x07, - 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x22, 0x26, 0x0a, 0x14, 0x56, 0x6f, 0x6c, 0x75, 0x6d, - 0x65, 0x73, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, - 0x17, 0x0a, 0x15, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x27, 0x0a, 0x15, 0x56, 0x6f, 0x6c, 0x75, - 0x6d, 0x65, 0x73, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, - 0x64, 0x22, 0x5a, 0x0a, 0x16, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x49, 0x6e, 0x73, 0x70, - 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x06, 0x76, - 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x63, 0x6f, - 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x73, 0x2e, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x56, - 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x06, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x32, 0x91, 0x04, - 0x0a, 0x07, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x12, 0x80, 0x01, 0x0a, 0x0d, 0x56, 0x6f, - 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x36, 0x2e, 0x63, 0x6f, - 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x73, 0x2e, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x56, - 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x76, 0x6f, 0x6c, 0x75, - 0x6d, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x0b, - 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x34, 0x2e, 0x63, 0x6f, - 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x73, 0x2e, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x56, - 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, - 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x80, 0x01, 0x0a, 0x0d, 0x56, 0x6f, 0x6c, - 0x75, 0x6d, 0x65, 0x73, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x36, 0x2e, 0x63, 0x6f, 0x6d, - 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x73, 0x2e, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x6f, - 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x76, 0x6f, 0x6c, 0x75, 0x6d, - 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x83, 0x01, 0x0a, 0x0e, - 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x12, 0x37, - 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x2e, 0x76, - 0x31, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x6f, - 0x63, 0x6b, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, - 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, - 0x65, 0x73, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x42, 0x3f, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x65, 0x2d, 0x63, - 0x6c, 0x69, 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x3b, - 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_cli_server_protos_volumes_v1_volumes_proto_rawDescOnce sync.Once - file_cli_server_protos_volumes_v1_volumes_proto_rawDescData = file_cli_server_protos_volumes_v1_volumes_proto_rawDesc -) - -func file_cli_server_protos_volumes_v1_volumes_proto_rawDescGZIP() []byte { - file_cli_server_protos_volumes_v1_volumes_proto_rawDescOnce.Do(func() { - file_cli_server_protos_volumes_v1_volumes_proto_rawDescData = protoimpl.X.CompressGZIP(file_cli_server_protos_volumes_v1_volumes_proto_rawDescData) - }) - return file_cli_server_protos_volumes_v1_volumes_proto_rawDescData -} - -var file_cli_server_protos_volumes_v1_volumes_proto_msgTypes = make([]protoimpl.MessageInfo, 10) -var file_cli_server_protos_volumes_v1_volumes_proto_goTypes = []interface{}{ - (*Volume)(nil), // 0: com.docker.api.protos.volumes.v1.Volume - (*AciVolumeCreateOptions)(nil), // 1: com.docker.api.protos.volumes.v1.AciVolumeCreateOptions - (*VolumesCreateRequest)(nil), // 2: com.docker.api.protos.volumes.v1.VolumesCreateRequest - (*VolumesCreateResponse)(nil), // 3: com.docker.api.protos.volumes.v1.VolumesCreateResponse - (*VolumesListRequest)(nil), // 4: com.docker.api.protos.volumes.v1.VolumesListRequest - (*VolumesListResponse)(nil), // 5: com.docker.api.protos.volumes.v1.VolumesListResponse - (*VolumesDeleteRequest)(nil), // 6: com.docker.api.protos.volumes.v1.VolumesDeleteRequest - (*VolumesDeleteResponse)(nil), // 7: com.docker.api.protos.volumes.v1.VolumesDeleteResponse - (*VolumesInspectRequest)(nil), // 8: com.docker.api.protos.volumes.v1.VolumesInspectRequest - (*VolumesInspectResponse)(nil), // 9: com.docker.api.protos.volumes.v1.VolumesInspectResponse -} -var file_cli_server_protos_volumes_v1_volumes_proto_depIdxs = []int32{ - 1, // 0: com.docker.api.protos.volumes.v1.VolumesCreateRequest.aci_option:type_name -> com.docker.api.protos.volumes.v1.AciVolumeCreateOptions - 0, // 1: com.docker.api.protos.volumes.v1.VolumesCreateResponse.volume:type_name -> com.docker.api.protos.volumes.v1.Volume - 0, // 2: com.docker.api.protos.volumes.v1.VolumesListResponse.volumes:type_name -> com.docker.api.protos.volumes.v1.Volume - 0, // 3: com.docker.api.protos.volumes.v1.VolumesInspectResponse.volume:type_name -> com.docker.api.protos.volumes.v1.Volume - 2, // 4: com.docker.api.protos.volumes.v1.Volumes.VolumesCreate:input_type -> com.docker.api.protos.volumes.v1.VolumesCreateRequest - 4, // 5: com.docker.api.protos.volumes.v1.Volumes.VolumesList:input_type -> com.docker.api.protos.volumes.v1.VolumesListRequest - 6, // 6: com.docker.api.protos.volumes.v1.Volumes.VolumesDelete:input_type -> com.docker.api.protos.volumes.v1.VolumesDeleteRequest - 8, // 7: com.docker.api.protos.volumes.v1.Volumes.VolumesInspect:input_type -> com.docker.api.protos.volumes.v1.VolumesInspectRequest - 3, // 8: com.docker.api.protos.volumes.v1.Volumes.VolumesCreate:output_type -> com.docker.api.protos.volumes.v1.VolumesCreateResponse - 5, // 9: com.docker.api.protos.volumes.v1.Volumes.VolumesList:output_type -> com.docker.api.protos.volumes.v1.VolumesListResponse - 7, // 10: com.docker.api.protos.volumes.v1.Volumes.VolumesDelete:output_type -> com.docker.api.protos.volumes.v1.VolumesDeleteResponse - 9, // 11: com.docker.api.protos.volumes.v1.Volumes.VolumesInspect:output_type -> com.docker.api.protos.volumes.v1.VolumesInspectResponse - 8, // [8:12] is the sub-list for method output_type - 4, // [4:8] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name -} - -func init() { file_cli_server_protos_volumes_v1_volumes_proto_init() } -func file_cli_server_protos_volumes_v1_volumes_proto_init() { - if File_cli_server_protos_volumes_v1_volumes_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Volume); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AciVolumeCreateOptions); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VolumesCreateRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VolumesCreateResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VolumesListRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VolumesListResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VolumesDeleteRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VolumesDeleteResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VolumesInspectRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VolumesInspectResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_cli_server_protos_volumes_v1_volumes_proto_msgTypes[2].OneofWrappers = []interface{}{ - (*VolumesCreateRequest_AciOption)(nil), - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_cli_server_protos_volumes_v1_volumes_proto_rawDesc, - NumEnums: 0, - NumMessages: 10, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_cli_server_protos_volumes_v1_volumes_proto_goTypes, - DependencyIndexes: file_cli_server_protos_volumes_v1_volumes_proto_depIdxs, - MessageInfos: file_cli_server_protos_volumes_v1_volumes_proto_msgTypes, - }.Build() - File_cli_server_protos_volumes_v1_volumes_proto = out.File - file_cli_server_protos_volumes_v1_volumes_proto_rawDesc = nil - file_cli_server_protos_volumes_v1_volumes_proto_goTypes = nil - file_cli_server_protos_volumes_v1_volumes_proto_depIdxs = nil -} - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConnInterface - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion6 - -// VolumesClient is the client API for Volumes service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. -type VolumesClient interface { - VolumesCreate(ctx context.Context, in *VolumesCreateRequest, opts ...grpc.CallOption) (*VolumesCreateResponse, error) - VolumesList(ctx context.Context, in *VolumesListRequest, opts ...grpc.CallOption) (*VolumesListResponse, error) - VolumesDelete(ctx context.Context, in *VolumesDeleteRequest, opts ...grpc.CallOption) (*VolumesDeleteResponse, error) - VolumesInspect(ctx context.Context, in *VolumesInspectRequest, opts ...grpc.CallOption) (*VolumesInspectResponse, error) -} - -type volumesClient struct { - cc grpc.ClientConnInterface -} - -func NewVolumesClient(cc grpc.ClientConnInterface) VolumesClient { - return &volumesClient{cc} -} - -func (c *volumesClient) VolumesCreate(ctx context.Context, in *VolumesCreateRequest, opts ...grpc.CallOption) (*VolumesCreateResponse, error) { - out := new(VolumesCreateResponse) - err := c.cc.Invoke(ctx, "/com.docker.api.protos.volumes.v1.Volumes/VolumesCreate", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *volumesClient) VolumesList(ctx context.Context, in *VolumesListRequest, opts ...grpc.CallOption) (*VolumesListResponse, error) { - out := new(VolumesListResponse) - err := c.cc.Invoke(ctx, "/com.docker.api.protos.volumes.v1.Volumes/VolumesList", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *volumesClient) VolumesDelete(ctx context.Context, in *VolumesDeleteRequest, opts ...grpc.CallOption) (*VolumesDeleteResponse, error) { - out := new(VolumesDeleteResponse) - err := c.cc.Invoke(ctx, "/com.docker.api.protos.volumes.v1.Volumes/VolumesDelete", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *volumesClient) VolumesInspect(ctx context.Context, in *VolumesInspectRequest, opts ...grpc.CallOption) (*VolumesInspectResponse, error) { - out := new(VolumesInspectResponse) - err := c.cc.Invoke(ctx, "/com.docker.api.protos.volumes.v1.Volumes/VolumesInspect", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// VolumesServer is the server API for Volumes service. -type VolumesServer interface { - VolumesCreate(context.Context, *VolumesCreateRequest) (*VolumesCreateResponse, error) - VolumesList(context.Context, *VolumesListRequest) (*VolumesListResponse, error) - VolumesDelete(context.Context, *VolumesDeleteRequest) (*VolumesDeleteResponse, error) - VolumesInspect(context.Context, *VolumesInspectRequest) (*VolumesInspectResponse, error) -} - -// UnimplementedVolumesServer can be embedded to have forward compatible implementations. -type UnimplementedVolumesServer struct { -} - -func (*UnimplementedVolumesServer) VolumesCreate(context.Context, *VolumesCreateRequest) (*VolumesCreateResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method VolumesCreate not implemented") -} -func (*UnimplementedVolumesServer) VolumesList(context.Context, *VolumesListRequest) (*VolumesListResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method VolumesList not implemented") -} -func (*UnimplementedVolumesServer) VolumesDelete(context.Context, *VolumesDeleteRequest) (*VolumesDeleteResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method VolumesDelete not implemented") -} -func (*UnimplementedVolumesServer) VolumesInspect(context.Context, *VolumesInspectRequest) (*VolumesInspectResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method VolumesInspect not implemented") -} - -func RegisterVolumesServer(s *grpc.Server, srv VolumesServer) { - s.RegisterService(&_Volumes_serviceDesc, srv) -} - -func _Volumes_VolumesCreate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(VolumesCreateRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(VolumesServer).VolumesCreate(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.docker.api.protos.volumes.v1.Volumes/VolumesCreate", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(VolumesServer).VolumesCreate(ctx, req.(*VolumesCreateRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Volumes_VolumesList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(VolumesListRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(VolumesServer).VolumesList(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.docker.api.protos.volumes.v1.Volumes/VolumesList", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(VolumesServer).VolumesList(ctx, req.(*VolumesListRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Volumes_VolumesDelete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(VolumesDeleteRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(VolumesServer).VolumesDelete(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.docker.api.protos.volumes.v1.Volumes/VolumesDelete", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(VolumesServer).VolumesDelete(ctx, req.(*VolumesDeleteRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Volumes_VolumesInspect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(VolumesInspectRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(VolumesServer).VolumesInspect(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.docker.api.protos.volumes.v1.Volumes/VolumesInspect", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(VolumesServer).VolumesInspect(ctx, req.(*VolumesInspectRequest)) - } - return interceptor(ctx, in, info, handler) -} - -var _Volumes_serviceDesc = grpc.ServiceDesc{ - ServiceName: "com.docker.api.protos.volumes.v1.Volumes", - HandlerType: (*VolumesServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "VolumesCreate", - Handler: _Volumes_VolumesCreate_Handler, - }, - { - MethodName: "VolumesList", - Handler: _Volumes_VolumesList_Handler, - }, - { - MethodName: "VolumesDelete", - Handler: _Volumes_VolumesDelete_Handler, - }, - { - MethodName: "VolumesInspect", - Handler: _Volumes_VolumesInspect_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "cli/server/protos/volumes/v1/volumes.proto", -} diff --git a/cli/server/protos/volumes/v1/volumes.proto b/cli/server/protos/volumes/v1/volumes.proto deleted file mode 100644 index 4048621ed..000000000 --- a/cli/server/protos/volumes/v1/volumes.proto +++ /dev/null @@ -1,71 +0,0 @@ -// -// Copyright 2020 Docker Compose CLI authors - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto3"; - -package com.docker.api.protos.volumes.v1; - -import "google/protobuf/any.proto"; - -option go_package = "github.com/docker/compose-cli/cli/server/protos/volumes/v1;v1"; - -service Volumes { - rpc VolumesCreate(VolumesCreateRequest) returns (VolumesCreateResponse); - rpc VolumesList(VolumesListRequest) returns (VolumesListResponse); - rpc VolumesDelete(VolumesDeleteRequest) returns (VolumesDeleteResponse); - rpc VolumesInspect(VolumesInspectRequest) returns (VolumesInspectResponse); -} - -message Volume { - string id = 1; - string description = 2; -} - -message AciVolumeCreateOptions { - string storage_account = 1; -} - -message VolumesCreateRequest { - string name = 1; - oneof options { - AciVolumeCreateOptions aci_option = 2; - } -} - -message VolumesCreateResponse { - Volume volume = 1; -} - -message VolumesListRequest { -} - -message VolumesListResponse { - repeated Volume volumes = 1; -} - -message VolumesDeleteRequest { - string id = 1; -} - -message VolumesDeleteResponse { -} - -message VolumesInspectRequest { - string id = 1; -} - -message VolumesInspectResponse { - Volume volume = 1; -} diff --git a/cli/server/proxy/containers.go b/cli/server/proxy/containers.go deleted file mode 100644 index 802a09301..000000000 --- a/cli/server/proxy/containers.go +++ /dev/null @@ -1,201 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package proxy - -import ( - "context" - "errors" - - "github.com/docker/compose-cli/utils" - - "github.com/compose-spec/compose-go/types" - "github.com/containerd/containerd/platforms" - specs "github.com/opencontainers/image-spec/specs-go/v1" - - "github.com/docker/compose-cli/api/containers" - containersv1 "github.com/docker/compose-cli/cli/server/protos/containers/v1" - "github.com/docker/compose-cli/cli/server/proxy/streams" -) - -func portsToGrpc(ports []containers.Port) []*containersv1.Port { - var result []*containersv1.Port - for _, port := range ports { - result = append(result, &containersv1.Port{ - ContainerPort: port.ContainerPort, - HostPort: port.HostPort, - HostIp: port.HostIP, - Protocol: port.Protocol, - }) - } - - return result -} - -func (p *proxy) List(ctx context.Context, request *containersv1.ListRequest) (*containersv1.ListResponse, error) { - containerList, err := Client(ctx).ContainerService().List(ctx, request.GetAll()) - if err != nil { - return &containersv1.ListResponse{}, err - } - - response := &containersv1.ListResponse{ - Containers: []*containersv1.Container{}, - } - for _, container := range containerList { - response.Containers = append(response.Containers, toGrpcContainer(container)) - } - - return response, nil -} - -func (p *proxy) Start(ctx context.Context, request *containersv1.StartRequest) (*containersv1.StartResponse, error) { - return &containersv1.StartResponse{}, Client(ctx).ContainerService().Start(ctx, request.Id) -} - -func (p *proxy) Stop(ctx context.Context, request *containersv1.StopRequest) (*containersv1.StopResponse, error) { - timeoutValue := request.GetTimeout() - return &containersv1.StopResponse{}, Client(ctx).ContainerService().Stop(ctx, request.Id, &timeoutValue) -} - -func (p *proxy) Kill(ctx context.Context, request *containersv1.KillRequest) (*containersv1.KillResponse, error) { - signal := request.GetSignal() - return &containersv1.KillResponse{}, Client(ctx).ContainerService().Kill(ctx, request.Id, signal) -} - -func (p *proxy) Run(ctx context.Context, request *containersv1.RunRequest) (*containersv1.RunResponse, error) { - containerConfig, err := grpcContainerToContainerConfig(request) - if err != nil { - return nil, err - } - return &containersv1.RunResponse{}, Client(ctx).ContainerService().Run(ctx, containerConfig) -} - -func (p *proxy) Inspect(ctx context.Context, request *containersv1.InspectRequest) (*containersv1.InspectResponse, error) { - c, err := Client(ctx).ContainerService().Inspect(ctx, request.Id) - if err != nil { - return nil, err - } - response := &containersv1.InspectResponse{ - Container: toGrpcContainer(c), - } - return response, err -} - -func (p *proxy) Delete(ctx context.Context, request *containersv1.DeleteRequest) (*containersv1.DeleteResponse, error) { - return &containersv1.DeleteResponse{}, Client(ctx).ContainerService().Delete(ctx, request.Id, containers.DeleteRequest{ - Force: request.Force, - }) -} - -func (p *proxy) Exec(ctx context.Context, request *containersv1.ExecRequest) (*containersv1.ExecResponse, error) { - p.mu.Lock() - stream, ok := p.streams[request.StreamId] - p.mu.Unlock() - if !ok { - return &containersv1.ExecResponse{}, errors.New("unknown stream id") - } - - io := &streams.IO{ - Stream: stream, - } - - return &containersv1.ExecResponse{}, Client(ctx).ContainerService().Exec(ctx, request.GetId(), containers.ExecRequest{ - Stdin: io, - Stdout: io, - Command: request.GetCommand(), - Tty: request.GetTty(), - }) -} - -func (p *proxy) Logs(request *containersv1.LogsRequest, stream containersv1.Containers_LogsServer) error { - return Client(stream.Context()).ContainerService().Logs(stream.Context(), request.GetContainerId(), containers.LogsRequest{ - Follow: request.Follow, - Writer: &streams.Log{ - Stream: stream, - }, - }) -} - -func toGrpcContainer(c containers.Container) *containersv1.Container { - return &containersv1.Container{ - Id: c.ID, - Image: c.Image, - Status: c.Status, - Command: c.Command, - CpuTime: c.CPUTime, - MemoryUsage: c.MemoryUsage, - Platform: c.Platform, - PidsCurrent: c.PidsCurrent, - PidsLimit: c.PidsLimit, - Labels: c.Config.Labels, - Ports: portsToGrpc(c.Ports), - HostConfig: &containersv1.HostConfig{ - MemoryReservation: c.HostConfig.MemoryReservation, - MemoryLimit: c.HostConfig.MemoryLimit, - CpuReservation: uint64(c.HostConfig.CPUReservation), - CpuLimit: uint64(c.HostConfig.CPULimit), - RestartPolicy: c.HostConfig.RestartPolicy, - AutoRemove: c.HostConfig.AutoRemove, - }, - Healthcheck: &containersv1.Healthcheck{ - Disable: c.Healthcheck.Disable, - Test: c.Healthcheck.Test, - Interval: int64(c.Healthcheck.Interval), - }, - } -} - -func grpcContainerToContainerConfig(request *containersv1.RunRequest) (containers.ContainerConfig, error) { - var ports []containers.Port - for _, p := range request.GetPorts() { - ports = append(ports, containers.Port{ - ContainerPort: p.ContainerPort, - HostIP: p.HostIp, - HostPort: p.HostPort, - Protocol: p.Protocol, - }) - } - - var platform *specs.Platform - - if request.Platform != "" { - p, err := platforms.Parse(request.Platform) - if err != nil { - return containers.ContainerConfig{}, err - } - platform = &p - } - - return containers.ContainerConfig{ - ID: request.GetId(), - Image: request.GetImage(), - Command: request.GetCommand(), - Ports: ports, - Labels: request.GetLabels(), - Volumes: request.GetVolumes(), - MemLimit: utils.MemBytes(request.GetMemoryLimit()), - CPULimit: float64(request.GetCpuLimit()), - RestartPolicyCondition: request.RestartPolicyCondition, - Environment: request.Environment, - AutoRemove: request.AutoRemove, - Healthcheck: containers.Healthcheck{ - Disable: request.GetHealthcheck().GetDisable(), - Test: request.GetHealthcheck().GetTest(), - Interval: types.Duration(request.GetHealthcheck().GetInterval()), - }, - Platform: platform, - }, nil -} diff --git a/cli/server/proxy/containers_test.go b/cli/server/proxy/containers_test.go deleted file mode 100644 index 701175acc..000000000 --- a/cli/server/proxy/containers_test.go +++ /dev/null @@ -1,72 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package proxy - -import ( - "testing" - - "github.com/docker/compose-cli/utils" - - "gotest.tools/v3/assert" - - "github.com/docker/compose-cli/api/containers" - containersv1 "github.com/docker/compose-cli/cli/server/protos/containers/v1" -) - -func TestGrpcContainerToContainerConfig(t *testing.T) { - r := &containersv1.RunRequest{ - Id: "myId", - Image: "myImage", - Ports: []*containersv1.Port{ - { - HostPort: 8080, - ContainerPort: 80, - Protocol: "tcp", - HostIp: "42.42.42.42", - }, - }, - Labels: map[string]string{ - "mykey": "mylabel", - }, - Volumes: []string{ - "myvolume", - }, - MemoryLimit: 41, - CpuLimit: 42, - Environment: []string{"PROTOVAR=VALUE"}, - } - - cc, err := grpcContainerToContainerConfig(r) - assert.NilError(t, err) - assert.Equal(t, cc.ID, "myId") - assert.Equal(t, cc.Image, "myImage") - assert.Equal(t, cc.MemLimit, utils.MemBytes(41)) - assert.Equal(t, cc.CPULimit, float64(42)) - assert.DeepEqual(t, cc.Volumes, []string{"myvolume"}) - assert.DeepEqual(t, cc.Ports, []containers.Port{ - { - HostPort: uint32(8080), - ContainerPort: 80, - Protocol: "tcp", - HostIP: "42.42.42.42", - }, - }) - assert.DeepEqual(t, cc.Labels, map[string]string{ - "mykey": "mylabel", - }) - assert.DeepEqual(t, cc.Environment, []string{"PROTOVAR=VALUE"}) -} diff --git a/cli/server/proxy/contexts.go b/cli/server/proxy/contexts.go deleted file mode 100644 index 2d6868257..000000000 --- a/cli/server/proxy/contexts.go +++ /dev/null @@ -1,111 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package proxy - -import ( - "context" - - "github.com/docker/compose-cli/api/config" - "github.com/docker/compose-cli/api/context/store" - contextsv1 "github.com/docker/compose-cli/cli/server/protos/contexts/v1" -) - -type contextsProxy struct { - configDir string -} - -func (cp *contextsProxy) SetCurrent(ctx context.Context, request *contextsv1.SetCurrentRequest) (*contextsv1.SetCurrentResponse, error) { - if err := config.WriteCurrentContext(cp.configDir, request.GetName()); err != nil { - return &contextsv1.SetCurrentResponse{}, err - } - - return &contextsv1.SetCurrentResponse{}, nil -} - -func (cp *contextsProxy) List(ctx context.Context, request *contextsv1.ListRequest) (*contextsv1.ListResponse, error) { - s := store.Instance() - configFile, err := config.LoadFile(cp.configDir) - if err != nil { - return nil, err - } - contexts, err := s.List() - if err != nil { - return &contextsv1.ListResponse{}, err - } - - return convertContexts(contexts, configFile.CurrentContext), nil -} - -func convertContexts(contexts []*store.DockerContext, currentContext string) *contextsv1.ListResponse { - result := &contextsv1.ListResponse{} - - for _, c := range contexts { - endpointName := c.Type() - if c.Type() == store.DefaultContextType { - endpointName = "docker" - } - var endpoint interface{} = c.Endpoints[endpointName] - - context := contextsv1.Context{ - Name: c.Name, - ContextType: c.Type(), - Description: c.Metadata.Description, - Current: c.Name == currentContext, - } - switch c.Type() { - case store.DefaultContextType: - context.Endpoint = getDockerEndpoint(endpoint) - case store.AciContextType: - context.Endpoint = getAciEndpoint(endpoint) - case store.EcsContextType: - context.Endpoint = getEcsEndpoint(endpoint) - } - - result.Contexts = append(result.Contexts, &context) - } - return result -} - -func getDockerEndpoint(endpoint interface{}) *contextsv1.Context_DockerEndpoint { - typedEndpoint := endpoint.(*store.Endpoint) - return &contextsv1.Context_DockerEndpoint{ - DockerEndpoint: &contextsv1.DockerEndpoint{ - Host: typedEndpoint.Host, - }, - } -} - -func getAciEndpoint(endpoint interface{}) *contextsv1.Context_AciEndpoint { - typedEndpoint := endpoint.(*store.AciContext) - return &contextsv1.Context_AciEndpoint{ - AciEndpoint: &contextsv1.AciEndpoint{ - ResourceGroup: typedEndpoint.ResourceGroup, - Region: typedEndpoint.Location, - SubscriptionId: typedEndpoint.SubscriptionID, - }, - } -} - -func getEcsEndpoint(endpoint interface{}) *contextsv1.Context_EcsEndpoint { - typedEndpoint := endpoint.(*store.EcsContext) - return &contextsv1.Context_EcsEndpoint{ - EcsEndpoint: &contextsv1.EcsEndpoint{ - FromEnvironment: typedEndpoint.CredentialsFromEnv, - Profile: typedEndpoint.Profile, - }, - } -} diff --git a/cli/server/proxy/contexts_test.go b/cli/server/proxy/contexts_test.go deleted file mode 100644 index c05422936..000000000 --- a/cli/server/proxy/contexts_test.go +++ /dev/null @@ -1,112 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package proxy - -import ( - "testing" - - "gotest.tools/v3/assert" - - "github.com/google/go-cmp/cmp/cmpopts" - - "github.com/docker/compose-cli/api/context/store" - contextsv1 "github.com/docker/compose-cli/cli/server/protos/contexts/v1" -) - -func TestConvertContext(t *testing.T) { - contexts := []*store.DockerContext{ - { - Name: store.DefaultContextName, - Metadata: store.ContextMetadata{ - Description: "description 1", - Type: store.DefaultContextType, - }, - Endpoints: map[string]interface{}{ - "docker": &store.Endpoint{ - Host: "unix://var/run/docker.sock", - }, - }, - }, - { - Name: "acicontext", - Metadata: store.ContextMetadata{ - Description: "group1@eastus", - Type: store.AciContextType, - }, - Endpoints: map[string]interface{}{ - "aci": &store.AciContext{ - Location: "eastus", - ResourceGroup: "group1", - SubscriptionID: "Subscription id", - }, - }, - }, - { - Name: "ecscontext", - Metadata: store.ContextMetadata{ - Description: "ecs description", - Type: store.EcsContextType, - }, - Endpoints: map[string]interface{}{ - "ecs": &store.EcsContext{ - CredentialsFromEnv: false, - Profile: "awsprofile", - }, - }, - }, - } - converted := convertContexts(contexts, "acicontext") - expected := []*contextsv1.Context{ - { - Name: store.DefaultContextName, - Current: false, - ContextType: store.DefaultContextType, - Description: "description 1", - Endpoint: &contextsv1.Context_DockerEndpoint{ - DockerEndpoint: &contextsv1.DockerEndpoint{ - Host: "unix://var/run/docker.sock", - }, - }, - }, - { - Name: "acicontext", - Current: true, - ContextType: store.AciContextType, - Description: "group1@eastus", - Endpoint: &contextsv1.Context_AciEndpoint{ - AciEndpoint: &contextsv1.AciEndpoint{ - Region: "eastus", - ResourceGroup: "group1", - SubscriptionId: "Subscription id", - }, - }, - }, - { - Name: "ecscontext", - Current: false, - ContextType: store.EcsContextType, - Description: "ecs description", - Endpoint: &contextsv1.Context_EcsEndpoint{ - EcsEndpoint: &contextsv1.EcsEndpoint{ - FromEnvironment: false, - Profile: "awsprofile", - }, - }, - }, - } - assert.DeepEqual(t, converted.Contexts, expected, cmpopts.IgnoreUnexported(contextsv1.Context{}, contextsv1.DockerEndpoint{}, contextsv1.AciEndpoint{}, contextsv1.EcsEndpoint{})) -} diff --git a/cli/server/proxy/proxy.go b/cli/server/proxy/proxy.go deleted file mode 100644 index 52ddb215c..000000000 --- a/cli/server/proxy/proxy.go +++ /dev/null @@ -1,75 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package proxy - -import ( - "context" - "sync" - - "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/api/config" - containersv1 "github.com/docker/compose-cli/cli/server/protos/containers/v1" - contextsv1 "github.com/docker/compose-cli/cli/server/protos/contexts/v1" - streamsv1 "github.com/docker/compose-cli/cli/server/protos/streams/v1" - volumesv1 "github.com/docker/compose-cli/cli/server/protos/volumes/v1" - "github.com/docker/compose-cli/cli/server/proxy/streams" -) - -type clientKey struct{} - -// WithClient adds the client to the context -func WithClient(ctx context.Context, c *client.Client) context.Context { - return context.WithValue(ctx, clientKey{}, c) -} - -// Client returns the client from the context -func Client(ctx context.Context) *client.Client { - c, _ := ctx.Value(clientKey{}).(*client.Client) - return c -} - -// Proxy implements the gRPC server and forwards the actions -// to the right backend -type Proxy interface { - containersv1.ContainersServer - streamsv1.StreamingServer - volumesv1.VolumesServer - ContextsProxy() contextsv1.ContextsServer -} - -type proxy struct { - configDir string - mu sync.Mutex - streams map[string]*streams.Stream - contextsProxy *contextsProxy -} - -// New creates a new proxy server -func New(ctx context.Context) Proxy { - configDir := config.Dir() - return &proxy{ - configDir: configDir, - streams: map[string]*streams.Stream{}, - contextsProxy: &contextsProxy{ - configDir: configDir, - }, - } -} - -func (p *proxy) ContextsProxy() contextsv1.ContextsServer { - return p.contextsProxy -} diff --git a/cli/server/proxy/streams.go b/cli/server/proxy/streams.go deleted file mode 100644 index 763d61acc..000000000 --- a/cli/server/proxy/streams.go +++ /dev/null @@ -1,67 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package proxy - -import ( - "github.com/hashicorp/go-uuid" - "github.com/sirupsen/logrus" - "google.golang.org/grpc/metadata" - - streamsv1 "github.com/docker/compose-cli/cli/server/protos/streams/v1" - "github.com/docker/compose-cli/cli/server/proxy/streams" -) - -func (p *proxy) NewStream(stream streamsv1.Streaming_NewStreamServer) error { - var ( - ctx = stream.Context() - ) - id, err := uuid.GenerateUUID() - if err != nil { - return err - } - md := metadata.New(map[string]string{ - "id": id, - }) - - // return the id of the stream to the client - if err := stream.SendHeader(md); err != nil { - return err - } - - errc := make(chan error) - - p.mu.Lock() - p.streams[id] = &streams.Stream{ - Streaming_NewStreamServer: stream, - ErrChan: errc, - } - p.mu.Unlock() - - defer func() { - p.mu.Lock() - delete(p.streams, id) - p.mu.Unlock() - }() - - select { - case err := <-errc: - return err - case <-ctx.Done(): - logrus.Debug("client context canceled") - return ctx.Err() - } -} diff --git a/cli/server/proxy/streams/io.go b/cli/server/proxy/streams/io.go deleted file mode 100644 index 205499728..000000000 --- a/cli/server/proxy/streams/io.go +++ /dev/null @@ -1,62 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package streams - -import ( - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" - - streamsv1 "github.com/docker/compose-cli/cli/server/protos/streams/v1" -) - -// IO implements an io.ReadWriter that forwards everything to the stream -type IO struct { - Stream *Stream -} - -func (io *IO) Read(p []byte) (int, error) { - a, err := io.Stream.Recv() - if err != nil { - return 0, err - } - - var m streamsv1.BytesMessage - err = anypb.UnmarshalTo(a, &m, proto.UnmarshalOptions{}) - if err != nil { - return 0, err - } - - return copy(p, m.Value), nil -} - -func (io *IO) Write(p []byte) (n int, err error) { - if len(p) == 0 { - return 0, nil - } - - message := streamsv1.BytesMessage{ - Type: streamsv1.IOStream_STDOUT, - Value: p, - } - - m, err := anypb.New(&message) - if err != nil { - return 0, err - } - - return len(message.Value), io.Stream.SendMsg(m) -} diff --git a/cli/server/proxy/streams/logs.go b/cli/server/proxy/streams/logs.go deleted file mode 100644 index 52cff554a..000000000 --- a/cli/server/proxy/streams/logs.go +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package streams - -import ( - "io" - - "google.golang.org/grpc" - - containersv1 "github.com/docker/compose-cli/cli/server/protos/containers/v1" -) - -// Log implements an io.Writer that proxies logs over a gRPC stream -type Log struct { - Stream grpc.ServerStream -} - -func newStreamWriter(stream grpc.ServerStream) io.Writer { - return &Log{ - Stream: stream, - } -} - -func (w *Log) Write(p []byte) (n int, err error) { - return len(p), w.Stream.SendMsg(&containersv1.LogsResponse{ - Value: p, - }) -} diff --git a/cli/server/proxy/streams/logs_test.go b/cli/server/proxy/streams/logs_test.go deleted file mode 100644 index eaa60b23c..000000000 --- a/cli/server/proxy/streams/logs_test.go +++ /dev/null @@ -1,77 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package streams - -import ( - "context" - "testing" - - "google.golang.org/grpc/metadata" - "gotest.tools/v3/assert" - "gotest.tools/v3/assert/cmp" - - v1 "github.com/docker/compose-cli/cli/server/protos/containers/v1" -) - -type logServer struct { - logs interface{} -} - -func (ls *logServer) Send(response *v1.LogsResponse) error { - return nil -} - -func (ls *logServer) SetHeader(metadata.MD) error { - return nil -} - -func (ls *logServer) SendHeader(metadata.MD) error { - return nil -} - -func (ls *logServer) SetTrailer(metadata.MD) { -} - -func (ls *logServer) Context() context.Context { - return nil -} - -func (ls *logServer) SendMsg(m interface{}) error { - ls.logs = m - return nil -} - -func (ls *logServer) RecvMsg(m interface{}) error { - return nil -} - -func TestLogStreamWriter(t *testing.T) { - ls := &logServer{} - sw := newStreamWriter(ls) - in := []byte{104, 101, 108, 108, 111} - expected := &v1.LogsResponse{ - Value: in, - } - - l, err := sw.Write(in) - - assert.NilError(t, err) - assert.Assert(t, cmp.Len(in, l)) - logs, ok := (ls.logs).(*v1.LogsResponse) - assert.Assert(t, ok) - assert.DeepEqual(t, logs.Value, expected.Value) -} diff --git a/cli/server/proxy/streams/stream.go b/cli/server/proxy/streams/stream.go deleted file mode 100644 index e10b3962b..000000000 --- a/cli/server/proxy/streams/stream.go +++ /dev/null @@ -1,47 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package streams - -import ( - "sync" - - streamsv1 "github.com/docker/compose-cli/cli/server/protos/streams/v1" -) - -// Stream is a bidirectional stream for container IO -type Stream struct { - streamsv1.Streaming_NewStreamServer - - errm sync.Mutex - ErrChan chan<- error -} - -// CloseWithError sends the result of an action to the errChan or nil -// if no erros -func (s *Stream) CloseWithError(err error) error { - s.errm.Lock() - defer s.errm.Unlock() - - if s.ErrChan != nil { - if err != nil { - s.ErrChan <- err - } - close(s.ErrChan) - s.ErrChan = nil - } - return nil -} diff --git a/cli/server/proxy/streams/stream_test.go b/cli/server/proxy/streams/stream_test.go deleted file mode 100644 index 54bd221a0..000000000 --- a/cli/server/proxy/streams/stream_test.go +++ /dev/null @@ -1,140 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package streams - -import ( - "context" - "errors" - "testing" - - "google.golang.org/grpc/metadata" - "google.golang.org/protobuf/types/known/anypb" - - "github.com/golang/protobuf/ptypes/any" - "gotest.tools/v3/assert" - "gotest.tools/v3/assert/cmp" - - streamsv1 "github.com/docker/compose-cli/cli/server/protos/streams/v1" -) - -type byteStream struct { - recvResult *any.Any - recvErr error - - sendResult interface{} -} - -func (bs *byteStream) SetHeader(metadata.MD) error { - return nil -} - -func (bs *byteStream) SendHeader(metadata.MD) error { - return nil -} - -func (bs *byteStream) SetTrailer(metadata.MD) { -} - -func (bs *byteStream) Context() context.Context { - return nil -} - -func (bs *byteStream) SendMsg(m interface{}) error { - bs.sendResult = m - return nil -} - -func (bs *byteStream) Send(*any.Any) error { - return nil -} - -func (bs *byteStream) Recv() (*any.Any, error) { - return bs.recvResult, bs.recvErr -} - -func (bs *byteStream) RecvMsg(m interface{}) error { - return nil -} - -func getReader(t *testing.T, in []byte, errResult error) IO { - message := streamsv1.BytesMessage{ - Type: streamsv1.IOStream_STDOUT, - Value: in, - } - m, err := anypb.New(&message) - assert.NilError(t, err) - - return IO{ - Stream: &Stream{ - Streaming_NewStreamServer: &byteStream{ - recvResult: m, - recvErr: errResult, - }, - }, - } -} - -func getAny(t *testing.T, in []byte) *any.Any { - value, err := anypb.New(&streamsv1.BytesMessage{ - Type: streamsv1.IOStream_STDOUT, - Value: in, - }) - assert.NilError(t, err) - return value -} - -func TestStreamReader(t *testing.T) { - in := []byte{104, 101, 108, 108, 111} - r := getReader(t, in, nil) - buffer := make([]byte, 5) - - n, err := r.Read(buffer) - - assert.NilError(t, err) - assert.Equal(t, n, 5) - assert.DeepEqual(t, buffer, in) -} - -func TestStreamReaderError(t *testing.T) { - errResult := errors.New("err") - r := getReader(t, nil, errResult) - var buffer []byte - - n, err := r.Read(buffer) - - assert.Equal(t, n, 0) - assert.Error(t, err, errResult.Error()) -} - -func TestStreamWriter(t *testing.T) { - in := []byte{104, 101, 108, 108, 111} - expected := getAny(t, in) - - bs := byteStream{} - w := IO{ - Stream: &Stream{ - Streaming_NewStreamServer: &bs, - }, - } - - n, err := w.Write(in) - assert.NilError(t, err) - assert.Assert(t, cmp.Len(in, n)) - sendResult, ok := (bs.sendResult).(*anypb.Any) - assert.Assert(t, ok) - assert.DeepEqual(t, sendResult.Value, expected.Value) -} diff --git a/cli/server/proxy/volumes.go b/cli/server/proxy/volumes.go deleted file mode 100644 index 164d41510..000000000 --- a/cli/server/proxy/volumes.go +++ /dev/null @@ -1,87 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package proxy - -import ( - "context" - - "github.com/docker/compose-cli/aci" - "github.com/docker/compose-cli/api/volumes" - volumesv1 "github.com/docker/compose-cli/cli/server/protos/volumes/v1" -) - -// VolumesCreate creates a volume. -func (p *proxy) VolumesCreate(ctx context.Context, req *volumesv1.VolumesCreateRequest) (*volumesv1.VolumesCreateResponse, error) { - storageAccount := "" - aciReq := req.GetAciOption() - if aciReq != nil { - storageAccount = aciReq.StorageAccount - } - aciOpts := aci.VolumeCreateOptions{ - Account: storageAccount, - } - - v, err := Client(ctx).VolumeService().Create(ctx, req.Name, aciOpts) - if err != nil { - return &volumesv1.VolumesCreateResponse{}, err - } - - return &volumesv1.VolumesCreateResponse{ - Volume: toGrpcVolume(v), - }, nil -} - -// VolumesList lists the volumes. -func (p *proxy) VolumesList(ctx context.Context, req *volumesv1.VolumesListRequest) (*volumesv1.VolumesListResponse, error) { - volumeList, err := Client(ctx).VolumeService().List(ctx) - if err != nil { - return &volumesv1.VolumesListResponse{}, err - } - - return &volumesv1.VolumesListResponse{ - Volumes: toGrpcVolumeList(volumeList), - }, nil -} - -// VolumesDelete deletes a volume. -func (p *proxy) VolumesDelete(ctx context.Context, req *volumesv1.VolumesDeleteRequest) (*volumesv1.VolumesDeleteResponse, error) { - err := Client(ctx).VolumeService().Delete(ctx, req.Id, nil) - return &volumesv1.VolumesDeleteResponse{}, err -} - -// VolumesInspect inspects a volume. -func (p *proxy) VolumesInspect(ctx context.Context, req *volumesv1.VolumesInspectRequest) (*volumesv1.VolumesInspectResponse, error) { - v, err := Client(ctx).VolumeService().Inspect(ctx, req.Id) - return &volumesv1.VolumesInspectResponse{ - Volume: toGrpcVolume(v), - }, err -} - -func toGrpcVolumeList(volumeList []volumes.Volume) []*volumesv1.Volume { - var ret []*volumesv1.Volume - for _, v := range volumeList { - ret = append(ret, toGrpcVolume(v)) - } - return ret -} - -func toGrpcVolume(v volumes.Volume) *volumesv1.Volume { - return &volumesv1.Volume{ - Id: v.ID, - Description: v.Description, - } -} diff --git a/cli/server/server.go b/cli/server/server.go deleted file mode 100644 index a5511af13..000000000 --- a/cli/server/server.go +++ /dev/null @@ -1,52 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package server - -import ( - "context" - "net" - "strings" - - "google.golang.org/grpc" - "google.golang.org/grpc/health" - "google.golang.org/grpc/health/grpc_health_v1" - - "github.com/docker/compose-cli/cli/metrics" -) - -// New returns a new GRPC server. -func New(ctx context.Context) *grpc.Server { - s := grpc.NewServer( - grpc.ChainUnaryInterceptor( - unaryServerInterceptor(ctx), - metricsServerInterceptor(metrics.NewClient()), - ), - grpc.StreamInterceptor(streamServerInterceptor(ctx)), - ) - hs := health.NewServer() - grpc_health_v1.RegisterHealthServer(s, hs) - return s -} - -// CreateListener creates a listener either on tcp://, or local listener, -// supporting unix:// for unix socket or npipe:// for named pipes on windows -func CreateListener(address string) (net.Listener, error) { - if strings.HasPrefix(address, "tcp://") { - return net.Listen("tcp", strings.TrimPrefix(address, "tcp://")) - } - return createLocalListener(address) -} diff --git a/cli/server/socket_unix.go b/cli/server/socket_unix.go deleted file mode 100644 index 5e597d06b..000000000 --- a/cli/server/socket_unix.go +++ /dev/null @@ -1,32 +0,0 @@ -// +build !windows - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package server - -import ( - "errors" - "net" - "strings" -) - -func createLocalListener(address string) (net.Listener, error) { - if !strings.HasPrefix(address, "unix://") { - return nil, errors.New("Cannot parse address, must start with unix:// or tcp:// : " + address) - } - return net.Listen("unix", strings.TrimPrefix(address, "unix://")) -} diff --git a/cli/server/socket_windows.go b/cli/server/socket_windows.go deleted file mode 100644 index cf95f4022..000000000 --- a/cli/server/socket_windows.go +++ /dev/null @@ -1,38 +0,0 @@ -// +build windows - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package server - -import ( - "errors" - "net" - "strings" - - "github.com/Microsoft/go-winio" -) - -func createLocalListener(address string) (net.Listener, error) { - if !strings.HasPrefix(address, "npipe://") { - return nil, errors.New("Cannot parse address, must start with npipe:// or tcp:// : " + address) - } - return winio.ListenPipe(strings.TrimPrefix(address, "npipe://"), &winio.PipeConfig{ - MessageMode: true, // Use message mode so that CloseWrite() is supported - InputBufferSize: 65536, // Use 64KB buffers to improve performance - OutputBufferSize: 65536, - }) -} diff --git a/cmd/compose/build.go b/cmd/compose/build.go index dfe15f5bb..0172e1da7 100644 --- a/cmd/compose/build.go +++ b/cmd/compose/build.go @@ -24,7 +24,7 @@ import ( "github.com/compose-spec/compose-go/types" "github.com/spf13/cobra" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" ) type buildOptions struct { diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index 260d5f818..b41e2333e 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -25,7 +25,7 @@ import ( "strings" "syscall" - "github.com/docker/compose-cli/cmd/formatter" + "github.com/docker/compose/v2/cmd/formatter" "github.com/sirupsen/logrus" @@ -37,8 +37,8 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/compose" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/compose" ) // Command defines a compose CLI command as a func with args diff --git a/cmd/compose/convert.go b/cmd/compose/convert.go index 6f9212632..8a779681e 100644 --- a/cmd/compose/convert.go +++ b/cmd/compose/convert.go @@ -33,8 +33,8 @@ import ( "github.com/opencontainers/go-digest" "github.com/spf13/cobra" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/compose" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/compose" ) type convertOptions struct { diff --git a/cmd/compose/cp.go b/cmd/compose/cp.go index 99f0952fd..f028a790b 100644 --- a/cmd/compose/cp.go +++ b/cmd/compose/cp.go @@ -23,7 +23,7 @@ import ( "github.com/docker/cli/cli" "github.com/spf13/cobra" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" ) type copyOptions struct { diff --git a/cmd/compose/create.go b/cmd/compose/create.go index 77c3087c4..266f6fc1d 100644 --- a/cmd/compose/create.go +++ b/cmd/compose/create.go @@ -24,7 +24,7 @@ import ( "github.com/compose-spec/compose-go/types" "github.com/spf13/cobra" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" ) type createOptions struct { diff --git a/cmd/compose/down.go b/cmd/compose/down.go index e851fb8c3..36878e96f 100644 --- a/cmd/compose/down.go +++ b/cmd/compose/down.go @@ -24,7 +24,7 @@ import ( "github.com/compose-spec/compose-go/types" "github.com/spf13/cobra" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" ) type downOptions struct { diff --git a/cmd/compose/events.go b/cmd/compose/events.go index 6f7368e95..2f9e711a0 100644 --- a/cmd/compose/events.go +++ b/cmd/compose/events.go @@ -21,7 +21,7 @@ import ( "encoding/json" "fmt" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" "github.com/spf13/cobra" ) diff --git a/cmd/compose/exec.go b/cmd/compose/exec.go index 653a44417..91c595315 100644 --- a/cmd/compose/exec.go +++ b/cmd/compose/exec.go @@ -25,7 +25,7 @@ import ( "github.com/docker/cli/cli" "github.com/spf13/cobra" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" ) type execOpts struct { diff --git a/cmd/compose/images.go b/cmd/compose/images.go index bed8a615a..eebef82eb 100644 --- a/cmd/compose/images.go +++ b/cmd/compose/images.go @@ -28,9 +28,9 @@ import ( "github.com/docker/go-units" "github.com/spf13/cobra" - "github.com/docker/compose-cli/cmd/formatter" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/utils" + "github.com/docker/compose/v2/cmd/formatter" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/utils" ) type imageOptions struct { diff --git a/cmd/compose/kill.go b/cmd/compose/kill.go index d4b33a115..a39fda67c 100644 --- a/cmd/compose/kill.go +++ b/cmd/compose/kill.go @@ -22,7 +22,7 @@ import ( "github.com/compose-spec/compose-go/types" "github.com/spf13/cobra" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" ) func killCommand(p *projectOptions, backend api.Service) *cobra.Command { diff --git a/cmd/compose/list.go b/cmd/compose/list.go index 0b1045b6f..6513e2a77 100644 --- a/cmd/compose/list.go +++ b/cmd/compose/list.go @@ -23,12 +23,12 @@ import ( "os" "strings" - "github.com/docker/compose-cli/cmd/formatter" + "github.com/docker/compose/v2/cmd/formatter" "github.com/docker/cli/opts" "github.com/spf13/cobra" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" ) type lsOptions struct { diff --git a/cmd/compose/logs.go b/cmd/compose/logs.go index 0d672bd74..ce094698a 100644 --- a/cmd/compose/logs.go +++ b/cmd/compose/logs.go @@ -20,11 +20,11 @@ import ( "context" "os" - "github.com/docker/compose-cli/cmd/formatter" + "github.com/docker/compose/v2/cmd/formatter" "github.com/spf13/cobra" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" ) type logsOptions struct { diff --git a/cmd/compose/pause.go b/cmd/compose/pause.go index 52c7fbb6a..fef6a0f3f 100644 --- a/cmd/compose/pause.go +++ b/cmd/compose/pause.go @@ -21,7 +21,7 @@ import ( "github.com/spf13/cobra" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" ) type pauseOptions struct { diff --git a/cmd/compose/port.go b/cmd/compose/port.go index 37ce0019b..6ffd6d1f9 100644 --- a/cmd/compose/port.go +++ b/cmd/compose/port.go @@ -23,7 +23,7 @@ import ( "github.com/spf13/cobra" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" ) type portOptions struct { diff --git a/cmd/compose/ps.go b/cmd/compose/ps.go index f86ec0cbd..cec6db9f4 100644 --- a/cmd/compose/ps.go +++ b/cmd/compose/ps.go @@ -25,14 +25,14 @@ import ( "strconv" "strings" - "github.com/docker/compose-cli/cmd/formatter" + "github.com/docker/compose/v2/cmd/formatter" formatter2 "github.com/docker/cli/cli/command/formatter" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/utils" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/utils" ) type psOptions struct { diff --git a/cmd/compose/pull.go b/cmd/compose/pull.go index c7213cc6c..59e948ed8 100644 --- a/cmd/compose/pull.go +++ b/cmd/compose/pull.go @@ -24,8 +24,8 @@ import ( "github.com/morikuni/aec" "github.com/spf13/cobra" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/utils" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/utils" ) type pullOptions struct { diff --git a/cmd/compose/push.go b/cmd/compose/push.go index 906578dd3..7e84e4c11 100644 --- a/cmd/compose/push.go +++ b/cmd/compose/push.go @@ -21,7 +21,7 @@ import ( "github.com/spf13/cobra" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" ) type pushOptions struct { diff --git a/cmd/compose/remove.go b/cmd/compose/remove.go index 5d41f7284..f84621387 100644 --- a/cmd/compose/remove.go +++ b/cmd/compose/remove.go @@ -19,7 +19,7 @@ package compose import ( "context" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" "github.com/spf13/cobra" ) diff --git a/cmd/compose/restart.go b/cmd/compose/restart.go index 8a022ed9d..600d2b6fa 100644 --- a/cmd/compose/restart.go +++ b/cmd/compose/restart.go @@ -22,7 +22,7 @@ import ( "github.com/spf13/cobra" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" ) type restartOptions struct { diff --git a/cmd/compose/run.go b/cmd/compose/run.go index cdaeeab08..d60f35184 100644 --- a/cmd/compose/run.go +++ b/cmd/compose/run.go @@ -29,8 +29,8 @@ import ( "github.com/spf13/cobra" "github.com/docker/cli/cli" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/progress" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/progress" ) type runOptions struct { diff --git a/cmd/compose/start.go b/cmd/compose/start.go index d3d46245b..8ad99c566 100644 --- a/cmd/compose/start.go +++ b/cmd/compose/start.go @@ -19,7 +19,7 @@ package compose import ( "context" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" "github.com/spf13/cobra" ) diff --git a/cmd/compose/stop.go b/cmd/compose/stop.go index 81b90ad93..d00d94f9d 100644 --- a/cmd/compose/stop.go +++ b/cmd/compose/stop.go @@ -22,7 +22,7 @@ import ( "github.com/spf13/cobra" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" ) type stopOptions struct { diff --git a/cmd/compose/top.go b/cmd/compose/top.go index 441deeaa2..60f3a1fac 100644 --- a/cmd/compose/top.go +++ b/cmd/compose/top.go @@ -27,7 +27,7 @@ import ( "github.com/spf13/cobra" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" ) type topOptions struct { diff --git a/cmd/compose/up.go b/cmd/compose/up.go index a056c83b4..7e31fb239 100644 --- a/cmd/compose/up.go +++ b/cmd/compose/up.go @@ -23,13 +23,13 @@ import ( "strconv" "strings" - "github.com/docker/compose-cli/cmd/formatter" + "github.com/docker/compose/v2/cmd/formatter" "github.com/compose-spec/compose-go/types" "github.com/spf13/cobra" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/utils" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/utils" ) // composeOptions hold options common to `up` and `run` to run compose project diff --git a/cmd/compose/version.go b/cmd/compose/version.go index b366a40cf..630a9a98f 100644 --- a/cmd/compose/version.go +++ b/cmd/compose/version.go @@ -19,11 +19,11 @@ package compose import ( "fmt" - "github.com/docker/compose-cli/cmd/formatter" + "github.com/docker/compose/v2/cmd/formatter" "github.com/spf13/cobra" - "github.com/docker/compose-cli/internal" + "github.com/docker/compose/v2/internal" ) type versionOptions struct { diff --git a/cmd/formatter/formatter.go b/cmd/formatter/formatter.go index c0452781c..cf26a7885 100644 --- a/cmd/formatter/formatter.go +++ b/cmd/formatter/formatter.go @@ -22,7 +22,7 @@ import ( "reflect" "strings" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" "github.com/pkg/errors" ) diff --git a/cmd/formatter/logs.go b/cmd/formatter/logs.go index 18f45cc9e..6b469a870 100644 --- a/cmd/formatter/logs.go +++ b/cmd/formatter/logs.go @@ -23,7 +23,7 @@ import ( "strconv" "strings" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" ) // NewLogConsumer creates a new LogConsumer diff --git a/cmd/main.go b/cmd/main.go index af2e64b6a..de3a54bc3 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -23,10 +23,10 @@ import ( "github.com/docker/cli/cli/command" "github.com/spf13/cobra" - commands "github.com/docker/compose-cli/cmd/compose" - "github.com/docker/compose-cli/internal" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/compose" + commands "github.com/docker/compose/v2/cmd/compose" + "github.com/docker/compose/v2/internal" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/compose" ) func init() { diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 872cc72da..000000000 --- a/docs/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Docker Compose CLI - -The general architecture of the CLI is described [here](architecture.md) - -# Azure Container Instances integration - -The Compose CLI can deploy single containers or Compose applications to ACI. - -* [ACI integration user guide](https://docs.docker.com/engine/context/aci-integration/) -* [Mapping `docker run` options to ACI deployments](aci-container-features.md) (single container) -* [Mapping Compose file options to ACI deployments](aci-compose-features.md) - -# Amazon Elastic Container Service - -The Compose CLI can deploy Compose applications to Amazon ECS. - -* [ECS integration user guide](https://docs.docker.com/engine/context/ecs-integration/) -* [General architecture](ecs-architecture.md) of the ECS integration -* [Compose examples](ecs-compose-examples.md) using various ECS integration features diff --git a/docs/aci-compose-features.md b/docs/aci-compose-features.md deleted file mode 100644 index 6d6766c64..000000000 --- a/docs/aci-compose-features.md +++ /dev/null @@ -1,239 +0,0 @@ ---- -title: ACI integration Compose features -description: Reference list of compose ACI features -keywords: Docker, Azure, Integration, ACI, Compose, cli, deploy, cloud ---- - -# Compose - Azure Container Instances mapping - -This document outlines the conversion of an application defined in a Compose file to ACI objects. -At a high-level, each Compose deployment is mapped to a single ACI container group. -Each service is mapped to a container in the container group. The Docker ACI integration does not allow scaling of services. - -## Compose fields mapping - -The table below lists supported Compose file fields and their ACI counterparts. - -__Legend:__ - -- __✓:__ Implemented -- __n:__ Not yet implemented -- __x:__ Not applicable / no available conversion - -| Keys |Map| Notes | -|--------------------------------|---|--------------------------------------------------------------| -| __Service__ | ✓ | -| service.service.build | x | Ignored. No image build support on ACI. -| service.cap_add, cap_drop | x | -| service.command | ✓ | Override container Command. On ACI, specifying `command` will override the image command and entrypoint, if the image has an command or entrypoint defined | -| service.configs | x | -| service.cgroup_parent | x | -| service.container_name | x | Service name is used as container name on ACI. -| service.credential_spec | x | -| service.deploy | ✓ | -| service.deploy.endpoint_mode | x | -| service.deploy.mode | x | -| service.deploy.replicas | x | Only one replica is started for each service. -| service.deploy.placement | x | -| service.deploy.update_config | x | -| service.deploy.resources | ✓ | Restriction: ACI resource limits cannot be greater than the sum of resource reservations for all containers in the container group. Using container limits that are greater than container reservations will cause containers in the same container group to compete with resources. -| service.deploy.restart_policy | ✓ | One of: `any`, `none`, `on-failure`. Restriction: All services must have the same restart policy. The entire ACI container group will be restarted if needed. -| service.deploy.labels | x | ACI does not have container-level labels. -| service.devices | x | -| service.depends_on | x | -| service.dns | x | -| service.dns_search | x | -| service.domainname | ✓ | Mapped to ACI DNSLabelName. Restriction: all services must specify the same `domainname`, if specified. `domainname` must be unique globally in .azurecontainer.io -| service.tmpfs | x | -| service.entrypoint | x | ACI only supports overriding the container command. -| service.env_file | ✓ | -| service.environment | ✓ | -| service.expose | x | -| service.extends | x | -| service.external_links | x | -| service.extra_hosts | x | -| service.group_add | x | -| service.healthcheck | ✓ | -| service.hostname | x | -| service.image | ✓ | Private images will be accessible if the user is logged into the corresponding registry at deploy time. Users will be automatically logged in to Azure Container Registry using their Azure login if possible. -| service.isolation | x | -| service.labels | x | ACI does not have container-level labels. -| service.links | x | -| service.logging | x | -| service.network_mode | x | -| service.networks | x | Communication between services is implemented by defining mapping for each service in the shared `/etc/hosts` file of the container group. Each service can resolve names for other services and the resulting network calls will be redirected to `localhost`. -| service.pid | x | -| service.ports | ✓ | Only symmetrical port mapping is supported in ACI. See [Exposing ports](#exposing-ports). -| service.secrets | ✓ | See [Secrets](#secrets). -| service.security_opt | x | -| service.stop_grace_period | x | -| service.stop_signal | x | -| service.sysctls | x | -| service.ulimits | x | -| service.userns_mode | x | -| service.volumes | ✓ | Mapped to AZure File Shares. See [Persistent volumes](#persistent-volumes). -| service.restart | x | Replaced by service.deployment.restart_policy -| | | -| __Volume__ | x | -| driver | ✓ | See [Persistent volumes](#persistent-volumes). -| driver_opts | ✓ | -| external | x | -| labels | x | -| | | -| __Secret__ | x | -| TBD | x | -| | | -| __Config__ | x | -| TBD | x | -| | | - - -## Logs - -Container logs can be obtained for each container with `docker logs `. -The Docker ACI integration does not currently support aggregated logs for containers in a Compose application, see https://github.com/docker/compose-cli/issues/803. - -## Exposing ports - -When one or more services expose ports, the entire ACI container group will be exposed and will get a public IP allocated. -As all services are mapped to containers in the same container group, only one service cannot expose a given port number. -[ACI does not support port mapping](https://feedback.azure.com/forums/602224-azure-container-instances/suggestions/34082284-support-for-port-mapping), so the source and target ports defined in the Compose file must be the same. - -When exposing ports, a service can also specify the service `domainname` field to set a DNS hostname. `domainname` will be used to specify the ACI DNS Label Name, and the ACI container group will be reachable at ..azurecontainer.io. -All services specifying a `domainname` must set the same value, as it is applied to the entire container group. -`domainname` must be unique globally in .azurecontainer.io - -## Persistent volumes - -Docker volumes are mapped to Azure file shares. Only the long Compose volume format is supported meaning that volumes must be defined in the `volume` section. -Volumes are defined with a name, the `driver` field must be set to `azure_file`, and `driver_options` must define the storage account and file share to use for the volume. -A service can then reference the volume by its name, and specify the target path to be mounted in the container. - -```yaml -services: - myservice: - image: nginx - volumes: - - mydata:/mount/testvolumes - -volumes: - mydata: - driver: azure_file - driver_opts: - share_name: myfileshare - storage_account_name: mystorageaccount -``` - -The short volume syntax is not allowed for ACI volumes, as it was designed for local path bind mounting when running local containers. -A Compose file can define several volumes, with different Azure file shares or storage accounts. - -Credentials for storage accounts will be automatically fetched at deployment time using the Azure login to retrieve the storage account key for each storage account used. - -## Secrets - -Secrets can be defined in compose files, and will need secret files available at deploy time next to the compose file. -The content of the secret file will be made available inside selected containers, by default under `/run/secrets/`. -External secrets are not supported with the ACI integration. - -```yaml -services: - nginx: - image: nginx - secrets: - - mysecret1 - db: - image: mysql - secrets: - - mysecret2 - -secrets: - mysecret1: - file: ./my_secret1.txt - mysecret2: - file: ./my_secret2.txt -``` - -The nginx container will have secret1 mounted as `/run/secrets/mysecret1`, the db container will have secret2 mounted as `/run/secrets/mysecret2` - -A target can also be specified to set the name of the mounted file or by specifying an absolute path where to mount the secret file - -```yaml -services: - nginx: - image: nginx - secrets: - - source: mysecret1 - target: renamedsecret1.txt - db: - image: mysql - secrets: - - source: mysecret1 - target: /mnt/dbmount/mysecretonmount1.txt - - source: mysecret2 - target: /mnt/dbmount/mysecretonmount2.txt - -secrets: - mysecret1: - file: ./my_secret1.txt - mysecret2: - file: ./my_secret2.txt -``` - -In this example the `nginx` service will have its secret mounted to `/run/secrets/renamedsecret1.txt` and `db` will have 2 files (`mysecretonmount1.txt` and `mysecretonmount2.txt`). -Both of them with be mounted in the same folder (`/mnt/dbmount/`). - - -**Note:** Relative file paths are not allowed in the target - -**Note:** Secret files cannot be mounted in a folder next to other existing files - -## Container Resources - -CPU and memory reservations and limits can be set in compose. -Resource limits must be greater than reservation. In ACI, setting resource limits different from resource reservation will cause containers in the same container group to compete for resources. Resource limits cannot be greater than the total resource reservation for the container group. (Therefore single containers cannot have resource limits different from resource reservations) - -```yaml -services: - db: - image: mysql - deploy: - resources: - reservations: - cpus: '2' - memory: 2G - limits: - cpus: '3' - memory: 3G - web: - image: nginx - deploy: - resources: - reservations: - cpus: '1.5' - memory: 1.5G -``` - -In this example, the db container will be allocated 2 CPUs and 2G of memory. It will be allowed to use up to 3 CPUs and 3G of memory, using some of the resources allocated to the web container. -The web container will have its limits set to the same values as reservations, by default. - -## Healthchecks - -A health check can be described in the `healthcheck` section of each service. This is translated to a `LivenessProbe` in ACI. If the health check fails then the container is considered unhealthy and terminated. -In order for the container to be restarted automatically, the service needs to have a restart policy other than `none` to be set. Note that the default restart policy if one isn't set is `any`. - -```yaml -services: - web: - image: nginx - deploy: - restart_policy: - condition: on-failure - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:80"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 40s -``` - -**Note:** that the `test` command can be a `string` or an array starting or not by `NONE`, `CMD`, `CMD-SHELL`. In the ACI implementation, these prefixes are ignored. diff --git a/docs/aci-container-features.md b/docs/aci-container-features.md deleted file mode 100644 index 19a9e94f1..000000000 --- a/docs/aci-container-features.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -title: ACI integration container features -description: Reference list of container ACI features -keywords: Docker, Azure, Integration, ACI, container, cli, deploy, cloud ---- -# Azure Container Instances: running single containerrs - -Single containers can be executed on ACI with the `docker run` command. -A single container is executed in its own ACI container group, that will container only one container. - -Containers can be listed with the `docker ps` command, and stopped and removed with `docker stop ` and `docker rm `. - -# Docker run options for ACI containers - -The table below lists supported `docker run` flags and their ACI counterparts. - -__Legend:__ - -- __✓:__ Implemented -- __n:__ Not yet implemented -- __x:__ Not applicable / no available conversion - -| Flag |Map| Notes -|-----------------------|---|---------------------------------| -| --cpus | ✓ | See [Container Resources](#container-resources). -| -d, --detach | ✓ | Detach from container logs when container starts. By default, the command line stays attached and follow container logs. -| --domainname | ✓ | See [Exposing ports](#exposing-ports). -| --e, --env | ✓ | Sets environment variable. -| --env-file | ✓ | Sets environment variable from and external file. -| --health-cmd | ✓ | Specify healthcheck command. See [Healthchecks](#healthchecks). -| --health-interval | ✓ | Specify healthcheck interval -| --health-retries | ✓ | Specify healthcheck number of retries -| --health-start-period | ✓ | Specify healthcheck initial delay -| --health-timeout | ✓ | Specify healthcheck timeout -| -l, --label | x | Unsupported in Docker ACI integration, due to limitations of ACI Tags. -| -m, --memory | ✓ | See [Container Resources](#container-resources). -| --name | ✓ | Provide a name for the container. Name must be unique withing the ACI resource group. a name is generated by default. -| -p, --publish | ✓ | See [Exposing ports](#exposing-ports). Only symetrical port mapping is supported in ACI. -| --restart | ✓ | Restart policy, must be one of: `always`, `no`, `on-failure`. -| --rm | x | Not supported as [ACI does not support auto-delete containers](https://feedback.azure.com/forums/602224-azure-container-instances/suggestions/34066633-support-auto-delete-of-aci-when-container-exits-no). -| -v, --volume | ✓ | See [Persistent Volumes](#persistent-volumes). - -## Exposing ports - -You can expose one or more ports of a container with `docker run -p :` -If ports are exposed when running a container, the corresponding ACI container group will be exposed with a public IP allocated and the required port(s) accessible. -> Note: [ACI does not support port mapping](https://feedback.azure.com/forums/602224-azure-container-instances/suggestions/34082284-support-for-port-mapping), so the same port number must be specified when using `-p :`. - -When exposing ports, a container can also specify the service `--domainname` flag to set a DNS hostname. `domainname` will be used to specify the ACI DNS Label Name, and the ACI container group will be reachable at `..azurecontainer.io`. -`domainname` must be unique globally in .azurecontainer.io - -## Persistent volumes - -Docker volumes are mapped to Azure File shares, each file share is part of an Azure Storage Account. -One or more volumes can be specified with `docker run -v /:`. - -A run command can use the `--volume` or `-v` flag several times for different volumes. The volumes can use the same or different storage accounts. The target paths for different volume mounts must be different and not overlap. -There is no support for mounting a single file, or mounting a subfolder from an Azure File Share. - -Credentials for storage accounts will be automatically fetched at deployment time using the Azure login to retrieve the storage account key for each storage account used. - -## Container Resources - -CPU and memory reservations can be set when running containers with `docker run --cpus 1.5 --memory 2G`. - -It is not possible to set resource limits that differ from resource reservation on single containers. -ACI allows setting resource limits for containers in a container group but these limits must stay within the reserved resources for the entire group. In the case of a single container deployed in a container group, the resource limits must be equal to the resource reservation. - -## Logs - -You can view container logs with the command `docker logs `. - -You can follow logs with the `--follow` (`-f`) option. -When running a container with `docker run`, by default the command line stays attached to container logs when the container starts. Use `docker run --detach` to not follow logs once the container starts. -> Note: Following ACI logs may have display issues especially when resizing a terminal that is following container logs. This is due to ACI providing raw log pulling but no streaming of logs. Logs are effectively pulled every 2 seconds when following logs. - -## Healthchecks - -A health check can be described using the flags prefixed by `--health-`. This is translated into `LivenessProbe` for ACI. If the health check fails then the container is considered unhealthy and terminated. -In order for the container to be restarted automatically, the container needs to be run with a restart policy (set by the `--restart` flag) other than `no`. Note that the default restart policy if one isn't set is `no`. - -In order to restart automatically, the container also need to have a restart policy set with `--restart` (`docker run` defaults to no restart policy) diff --git a/docs/architecture.md b/docs/architecture.md deleted file mode 100644 index c5ef2ffc9..000000000 --- a/docs/architecture.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Compose cli architecture -description: General Compose cli architecture -keywords: Docker, Amazon, Azure, Integration, ECS, ACI, Compose, architecture, mapping ---- - -# Architecture - -This CLI has the following high level design goals: -1. Provide a way for the Docker experience to be mapped to different container - runtimes -1. Provide a way to automatically generate high quality SDKs in popular - languages -1. Ensure that existing Docker CLI commands continue to work as before - -These constraints resulted in the following architecture: - -![CLI architecture](./images/cli-architecture.png) - -What follows is a list of useful links to help navigate the code: -* The CLI UX code is in [`cli/`](../cli) -* The backend interface is defined in [`backend/`](../backend) -* The API is defined by protobufs that can be found in [`protos/`](../protos) -* The API server is in [`server/`](../server) -* The context management and interface can be found in [`context/`](../api/context) -* The Node SDK is autogenerated (except for default endpoints managed by Docker Desktop), and can be found in - [`docker/node-sdk`](https://github.com/docker/node-sdk) \ No newline at end of file diff --git a/docs/ecs-architecture.md b/docs/ecs-architecture.md deleted file mode 100644 index c8d85d36f..000000000 --- a/docs/ecs-architecture.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -title: ECS integration architecture -description: Mapping of Docker compose entities to Amazon constructs -keywords: Docker, Amazon, Integration, ECS, Compose, architecture, mapping ---- -# Architecture - -ECS integration relies on CloudFormation to manage AWS resources as an atomic operation. -This document describes the mapping between compose application model and AWS components - -## Overview - -This diagram shows compose model and on same line AWS components that get created as equivalent resources - -``` -+----------+ +-------------+ +-------------------+ -| Project | . . . . . . . . . . . . . . | Cluster | . . . . . . . | LoadBalancer | -+-+--------+ +-------------+ +-------------------+ - | - | +----------+ +-------------++-------------------+ +-------------------+ - +----+ Service | . . . . . . . . . . | Service || TaskDefinition | | TargetGroup | - | +--+-------+ +-------------++-------------------+-+ +-------------------+ - | | | TaskRole | - | | +-------------------+-+ - | | x-aws-role, x-aws-policies . . . . . . . . | TaskExecutionRole | - | | +-------------------+ - | | +---------+ - | +--+ Deploy | - | | +---------+ +-------------------+ - | | x-aws-autoscaling . . . . . . | ScalableTarget | - | | +-------------------+---+ - | | | ScalingPolicy | - | | +-------------------+-+ - | | | AutoScalingRole | - | | +-------------------+ - | | - | | +---------+ +-------------+ +-------------------+ - | +--+ Ports | . . . . . . . | IngressRule +-----+ | Listener | - | | +---------+ +-------------+ | +-------------------+ - | | | - | | +---------+ +---------------+ +------------------+ - | +--+ Secrets | . . . . . . . | InitContainer | |TaskExecutionRole | - | | +---------+ +---------------+ +------------+-----+ - | | | | - | | +---------+ | | - | +--+ Volumes | | | - | | +---------+ | | - | | | | - | | +---------------+ | | +-------------------+ - | +--+ DeviceRequest | . . . . . . . . . . . . . . . | . . . . | . . . | CapacityProvider | - | +---------------+ | | +-------------------+--------+ - | | | | AutoscalingGroup | - | +------------+ +---------------+ | | +---------------------+ - +---+ Networks | . . . . . . . . . | SecurityGroup +---+ | | LaunchConfiguration | - | +------------+ +---------------+ | +---------------------+ - | | - | +------------+ +---------------+ | - +---+ Secret | . . . . . . . . . | Secret +--------------+ - +------------+ +---------------+ -``` - -Each compose application service is mapped to an ECS `Service`. A `TaskDefinition` is created according to compose definition. -Actual mapping is constrained by both Cloud platform and Fargate limitations. Such a `TaskDefinition` is set with a single container, -according to the compose model which doesn't offer a syntax to support sidecar containers. - -An IAM Role is created and configured as `TaskRole` to grant service access to additional AWS resources when required. For this -purpose, user can set `x-aws-policies` or define a fine grained `x-aws-role` IAM role document. - -Service's ports get mapped into security group's `IngressRule`s and load balancer `Listener`s. -Compose application with HTTP services only (using ports 80/443 or `x-aws-protocol` set to `http`) get an Application Load Balancer -created, otherwise a Network Load Balancer is used. - -A `TargetGroup` is created per service to dispatch traffic by load balancer to the matching containers - -Secrets bound to a service get translated into an `InitContainer` added to the service's `TaskDefinition`. This init container is -responsible to create a `/run/secrets` file for secret to match docker secret model and make application code portable. -A `TaskExecutionRole` is also created per service, and is updated to grant access to bound secrets. - -Services using a GPU (`DeviceRequest`) get the `Cluster` extended with an EC2 `CapacityProvider`, using an `AutoscalingGroup` to manage -EC2 resources allocation based on a `LaunchConfiguration`. The latter uses ECS recommended AMI and machine type for GPU. - -Service to declare `deploy.x-aws-autoscaling` get a `ScalingPolicy` created targeting specified the configured CPU usage metric diff --git a/docs/ecs-compose-examples.md b/docs/ecs-compose-examples.md deleted file mode 100644 index 9137b762a..000000000 --- a/docs/ecs-compose-examples.md +++ /dev/null @@ -1,302 +0,0 @@ ---- -title: ECS integration composefile examples -description: Examples of ECS compose files -keywords: Docker, Amazon, Integration, ECS, Compose, cli, deploy, cloud, sample ---- -# Compose file samples - ECS specific - - - -## Service - -A service mapping may define a Docker image and runtime constraints and container requirements. - -```yaml -services: - test: - image: "image" - command: "command" - entrypoint: "entrypoint" - environment: - - "FOO=BAR" - cap_add: - - SYS_PTRACE - cap_drop: - - SYSLOG - init: true - user: "user" - working_dir: "working_dir" -``` - - -###### Task size - -Set resource limits that will get translated to Fargate task size values: - -```yaml -services: - test: - image: nginx - deploy: - resources: - limits: - cpus: '0.5' - memory: 2048M -``` - -###### IAM roles - -Assign an existing user role to a task: - -```yaml -services: - test: - x-aws-policies: - - "arn:aws:iam::aws:policy/AmazonS3FullAccess" -``` - -###### IAM policies - -Assign an in-line IAM policy to a task: - -```yaml -services: - test: - x-aws-role: - Version: '2012-10-17' - Statement: - - Effect: Allow - Action: sqs:* - Resource: arn:aws:sqs:us-east-1:12345678:myqueue -``` - -###### Logging -Pass options to awslogs driver -```yaml -services: - foo: - image: nginx - logging: - options: - awslogs-datetime-pattern: "FOO" - -x-aws-logs_retention: 10 -``` - - -###### Autoscaling - -Set a CPU percent target -```yaml -services: - foo: - image: nginx - deploy: - x-aws-autoscaling: - cpu: 75 -``` - - -###### GPU -Set `generic_resources` for services that require accelerators as GPUs. -```yaml -services: - learning: - image: tensorflow/tensorflow:latest-gpus - deploy: - resources: - reservations: - memory: 32Gb - cpus: "32" - generic_resources: - - discrete_resource_spec: - kind: gpus - value: 2 -``` - - - - -##### Load Balancers - -When a service in the compose file exposes a port, a load balancer is being created and configured to distribute the traffic between all containers. - -There are 2 types of Load Balancers that can be created. For a service exposing a non-http port/protocol, a __Network Load Balancer (NLB)__ is created. Services with http/https ports/protocols get an __Application Load Balancer (ALB)__. - - There is only one load balancer created/configured for a Compose stack. If there are both http/non-http ports configured for services in a compose stack, an NLB is created. - -The compose file below configured only the http port,therefore, on deployment it gets an ALB created. - -```yaml -services: - app: - image: nginx - ports: - - 80:80 -``` -NLB is created for non-http port -```yaml -services: - app: - image: nginx - ports: - - 8080:8080 -``` - -To use the http protocol with custom ports and get an ALB, use the `x-aws-protocol` port property. -```yaml -services: - test: - image: nginx - ports: - - target: 8080 - x-aws-protocol: http -``` - -To re-use an external load balancer and avoid creating a dedicated one, set the top-level property `x-aws-loadbalancer` as below: -```yaml -x-aws-loadbalancer: "LoadBalancerName" -services: - app: - image: nginx - ports: - - 80:80 -``` - -Similarly, an external `VPC` and `Cluster` can be reused: - -```yaml -x-aws-vpc: "vpc-25435e" -x-aws-cluster: "ClusterName" - -services: - app: - image: nginx - ports: - - 80:80 -``` - -Keep in mind, that external resources are not managed as part of the compose stack's lifecycle. - - -## Volumes - -```yaml -services: - app: - image: nginx - volumes: - - data:/test -volumes: - data: -``` -To use of an external volume that has been previously created, set its id/ARN as the name: - -```yaml -services: - app: - image: nginx - volumes: - - data:/test - -volumes: - data: - external: true - name: "fs-f534645" -``` - -Customize volume configuration via `driver_opts` - -```yaml -services: - test: - image: nginx -volumes: - db-data: - driver_opts: - backup_policy: ENABLED - lifecycle_policy: AFTER_30_DAYS - performance_mode: maxIO - throughput_mode: provisioned - provisioned_throughput: 1024 -``` - -## Networks - -Networks are mapped to security groups. -```yaml -services: - test: - image: nginx -networks: - default: -``` -Using an external network/security group: -```yaml -services: - test: - image: nginx -networks: - default: - external: true - name: sg-123abc -``` - -## Secrets -Secrets are stored in __AWS SecretsManager__ as strings and are mounted to containers under `/run/secrets/`. -```yaml -services: - app: - image: nginx - ports: - - 80:80 - secrets: - - mysecret - -secrets: - mysecret: - file: ./secrets/mysecret.txt -``` - -When using external secrets, set a valid secret `ARN` under the `name` property: - -```yaml -services: - app: - image: nginx - secrets: - - foo_bar - -secrets: - foo_bar: - name: "arn:aws:secretsmanager:eu-west-3:xxx:secret:foo_bar" - external: true -``` - - -## Access private images -When a service is configured with an image from a private repository on Docker Hub, make sure you have configured pull credentials correctly before deploying the Compose stack. - -To create a pull credential, create a file with the following content: -```sh -$ cat creds.json -{ - "username":"DockerHubID", - "password":"GeneratedHubTokenOrPassword" -} -``` -To create the pull credential and retrieve the `ARN/ID` to use in the compose file run: -```sh -$ docker secret create pullcred /path/to/creds.json -arn:aws:secretsmanager:eu-west-3:xxx:secret:pullcred -``` - -Use the `ARN` in the output to set the `x-aws-pull_credentials` service property as below: -```yaml -services: - app: - image: DockerHubID/privateimage - x-aws-pull_credentials: arn:aws:secretsmanager:eu-west-3:xxx:secret:pullcred - ports: - - 80:80 -``` diff --git a/docs/ecs-compose-features.md b/docs/ecs-compose-features.md deleted file mode 100644 index 17aced963..000000000 --- a/docs/ecs-compose-features.md +++ /dev/null @@ -1,181 +0,0 @@ ---- -title: ECS integration Compose features -description: Reference list of compose ECS features -keywords: Docker, Amazon, Integration, ECS, Compose, cli, deploy, cloud ---- -# Compose - Amazon ECS mapping - -This document outlines the conversion of an application defined in a Compose file to AWS resources. -Each service is mapped to an ECS service in the project's cluster. - -## Compose fields mapping - -The table below lists supported Compose file fields and their AWS counterparts. - -__Legend:__ - -- __✓:__ Implemented -- __n:__ Not yet implemented -- __x:__ Not applicable / no available conversion - -| Keys |Map| Notes | -|--------------------------------|---|--------------------------------------------------------------| -| __Service__ | ✓ | -| service.service.build | x | Ignored. No image build support on AWS. -| service.cap_add, cap_drop | ✓ | Supported with [Fargate limitations](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html) -| service.command | ✓ | -| service.configs | x | -| service.cgroup_parent | x | -| service.container_name | x | -| service.credential_spec | x | -| service.deploy | ✓ | -| service.deploy.endpoint_mode | x | -| service.deploy.mode | x | -| service.deploy.replicas | ✓ | Set service initial scale. Auto-scaling, when enabled, will make this dynamic -| service.deploy.placement | ✓ | Used with EC2 support to select a machine type and AMI -| service.deploy.update_config | ✓ | -| service.deploy.resources | ✓ | Fargate resource is selected with the lowest instance type for configured memory and cpu -| service.deploy.restart_policy | ✓ | -| service.deploy.labels | ✓ | -| service.devices | x | -| service.depends_on | ✓ | Implemented using CloudFormation Depends_on -| service.dns | x | -| service.dns_search | x | -| service.domainname | x | -| service.tmpfs | x | Not supported on Fargate, see https://github.com/docker/compose-cli/issues/839 -| service.entrypoint | ✓ | -| service.env_file | ✓ | -| service.environment | ✓ | -| service.expose | x | -| service.extends | ✓ | -| service.external_links | x | -| service.extra_hosts | x | -| service.group_add | x | -| service.healthcheck | ✓ | This configures container level health check as reported on ECS console. Application Load Balancer will also check for HTTP service health by accessing `/` and expect a HTTP 200 status code. -| service.hostname | x | -| service.image | ✓ | Private images will be accessible by passing x-aws-pull_policy with ARN of a username+password secret -| service.isolation | x | -| service.labels | x | -| service.links | x | -| service.logging | ✓ | Can be used to customize CloudWatch Logs configuration -| service.network_mode | x | -| service.networks | x | Communication between services is implemented by SecurityGroups within the application VPC. -| service.pid | x | -| service.ports | ✓ | Only symetrical port mapping is supported in ECS. See [Exposing ports](#exposing-ports). -| service.secrets | ✓ | See [Secrets](#secrets). -| service.security_opt | x | -| service.stop_grace_period | x | -| service.stop_signal | x | -| service.sysctls | x | -| service.ulimits | ✓ | Only support `nofile` ulimit due to Fargate limitations -| service.userns_mode | x | -| service.volumes | ✓ | Mapped to EFS File Systems. See [Persistent volumes](#persistent-volumes). -| service.restart | x | Replaced by service.deployment.restart_policy -| | | -| __Volume__ | x | -| driver | ✓ | See [Persistent volumes](#persistent-volumes). -| driver_opts | ✓ | -| external | ✓ | `name` must be an EFS filesystem ID -| labels | x | -| | | -| __Secret__ | x | -| external | ✓ | `name` must be set to secret's ARN -| file | ✓ | file content will be uploaded into AWS Secret Manager -| | | -| __Config__ | x | -| | | - - -## Logs - -Application logs can be obtained container with `docker compose logs`. -The Docker ECS integration relies on AWS CloudWatch Logs to collect logs from all containers. CloudWatch can be customized by setting service `logging.driver_opts` -by passing configuration attributes prefixed with `awslogs-`. - -```yaml - test: - image: mycompany/webapp - logging: - driver-opts: - awslogs-datetime-pattern: "some-pattern" -``` - - -## Exposing ports - -When one or more services expose ports, a Load Balancer is created for the application. -As all services are exposed through the same Load Balancer, only one service can expose a given port number. -The source and target ports defined in the Compose file MUST be the same, as service-to-service communication don't go through the Load Balancer and could not -benefit from Listeners abstraction to assign a distinct published port. - -If services in the Compose file only expose ports 80 or 443, an Application Load Balancer is created, otherwise ECS integration will provision a Network Load Balancer. -HTTP services using distinct ports can force use of an ALB by claiming the http protocol with `x-aws-protocol` custom extension within the port declaration: - -```yaml - test: - image: mycompany/webapp - ports: - - target: 8080 - x-aws-protocol: http - -``` - -## Persistent volumes - -Docker volumes are mapped to EFS file systems. Volumes can be external (`name` must then be set to filesystem ID) or will be created when the application is -first deployed. `docker compose down` will NOT delete the filesystem, and it will be re-attached to the application on future runs. -`driver_opts` can be used to tweak the EFS filsystem. - -Volume mount can be customized to workaround Posix filesystem permission issues by setting user and group IDs to be used to write to filesystem, whatever user -is configured to run the container. - -```yaml -services: - myservice: - image: mycompany/webapp - volumes: - - mydata:/mount/testvolumes - -volumes: - mydata: - driver_opts: - performance-mode: maxIO - throughput-mode: bursting - uid: 0 - gid: 0 -``` - - -## Secrets - -Secrets can be defined in compose files, and will need secret files available at deploy time next to the compose file. -The content of the secret file will be made available inside selected containers, by default under `/run/secrets/`. -External secrets are also supported, `name` must then be set to secret's ARN - -```yaml -services: - nginx: - image: mycompany/webapp - secrets: - - mysecret - -secrets: - mysecret: - file: ./my_secret1.txt -``` - - -## Container Resources - -CPU and memory limits can be set in compose. Those are used to select the minimal [Fargate size](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/AWS_Fargate.html) that will match those limits. - -```yaml -services: - nginx: - image: mycompany/webapp - deploy: - resources: - limits: - cpu: '0.5' - memory: 2Gb -``` diff --git a/docs/images/cli-architecture-src.drawio b/docs/images/cli-architecture-src.drawio deleted file mode 100644 index 866400647..000000000 --- a/docs/images/cli-architecture-src.drawio +++ /dev/null @@ -1 +0,0 @@ -5VnbctowEP0aHtvBljHwCIZcpkknKdM2eRS2sNXIFiOLYPr1lbB8lUNpuHnSl0RaaWXr7J7jlegAJ0yuGVwG99RDpGN2vaQDJh3THBiG+CsNm9Rgg2Fq8Bn2UpNRGGb4N1LGrrKusIfiykROKeF4WTW6NIqQyys2yBhdV6ctKKk+dQl9pBlmLiS69Sf2eKC2ZfYL+w3CfpA92bDV/kKYTVY7iQPo0XXJBKYd4DBKedoKEwcRiV2GS+p39cZo/mIMRXwfB/PRfME4Gjx+H/Gb+yv8Y+IMP5mDdJlXSFZqx+pt+SaDgNFV5CG5SrcDxusAczRbQleOrkXMhS3gIRE9QzQXmBCHEspEP6KRmDT2YBxs3eW4/tZqI6+IcZSUTGoX14iGiLONmJLllAJUZVQO8LqIjwWULSjFJneEKif8fOkCNtFQyP0DioYG4hi6LyjypGPEEVtIuA7C9Qi4GcMqcMDSgTO7DcBZp8It20MJFOQJ+qmuyp8qTpTxgPo0guSO0qVC5xfifKPEA644rWKHEsyfSu1nudTnnupNErXytrPJOpHY4FO5U/KS3cJt28v8Yg4ZH0nhEQaXwDjGbma+wiR/pcjTJwljacqbEY/pirloF6xKJyHzEd81T8VVYr4zgRgikOPXqiQePxs0FjlUkCfhWzWNhNKy9nGoe2kOGf2zcqjgzXOZNo0cagMXwJ5cMOxWcQFoXBg93LYu+01bz37jrNnf0zXjTuDU/f50cbDM1oFla2ChBIZLAZAgCIo5jnzRmmely4XxAzX8LGBp+A3PqrR/K43PJKtFaVJUI8+5lh5cmrxfa/v7am2rpLavsWLqzFrEg1wjWsMD0A4enDyfB8fOU+X6QMWh8O0PBaifU1NCKa9a8PLXeH889SuAkXPbIgKYVq9lBLD+EwIcvSZuJwEMvTDqmDYRYI3nouHLxlfqyTJpNvmSDYlH5aMX50i9MjcaLsXOW2wauqz43x4cDantAb8CR8wZfUG1S8SGe0VIsB/JzEfyik0YJFzYhWSkBkLsefIxjfhXI3QMmap9p42BHgKrIQLmqSKQLbwrracJzur+7cGphandG5gVXHO1KKe22QCsfTJg9dN5gaMtTlRi49E8lv/c7Abro2Z9/ZTWGJ0m4QEni46u5rMg9aOrDxyJnlGPRE+LRP+c+gP20B+Hhksao6r8zFkhPnsI0uERJGjBD4vfBX7jMo70MRHd4kfItKQqfskF0z8= \ No newline at end of file diff --git a/docs/images/cli-architecture.png b/docs/images/cli-architecture.png deleted file mode 100644 index d713b1443..000000000 Binary files a/docs/images/cli-architecture.png and /dev/null differ diff --git a/docs/yaml/main/generate.go b/docs/yaml/main/generate.go index 00108de4e..4ab01a57c 100644 --- a/docs/yaml/main/generate.go +++ b/docs/yaml/main/generate.go @@ -27,8 +27,8 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/docker/compose-cli/cmd/compose" - . "github.com/docker/compose-cli/docs/yaml" + "github.com/docker/compose/v2/cmd/compose" + . "github.com/docker/compose/v2/docs/yaml" ) const descriptionSourcePath = "docs/reference/" diff --git a/ecs/autoscaling.go b/ecs/autoscaling.go deleted file mode 100644 index f40bc7148..000000000 --- a/ecs/autoscaling.go +++ /dev/null @@ -1,128 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "encoding/json" - "fmt" - - applicationautoscaling2 "github.com/aws/aws-sdk-go/service/applicationautoscaling" - "github.com/awslabs/goformation/v4/cloudformation" - "github.com/awslabs/goformation/v4/cloudformation/applicationautoscaling" - "github.com/awslabs/goformation/v4/cloudformation/iam" - "github.com/compose-spec/compose-go/types" -) - -type autoscalingConfig struct { - Memory int `json:"memory,omitempty"` - CPU int `json:"cpu,omitempty"` - Min int `json:"min,omitempty"` - Max int `json:"max,omitempty"` -} - -func (b *ecsAPIService) createAutoscalingPolicy(project *types.Project, resources awsResources, template *cloudformation.Template, service types.ServiceConfig) error { - if service.Deploy == nil { - return nil - } - v, ok := service.Deploy.Extensions[extensionAutoScaling] - if !ok { - return nil - } - - marshalled, err := json.Marshal(v) - if err != nil { - return err - } - var config autoscalingConfig - err = json.Unmarshal(marshalled, &config) - if err != nil { - return err - } - - if config.Memory != 0 && config.CPU != 0 { - return fmt.Errorf("%s can't be set with both cpu and memory targets", extensionAutoScaling) - } - if config.Max == 0 { - return fmt.Errorf("%s MUST define max replicas", extensionAutoScaling) - } - - role := fmt.Sprintf("%sAutoScalingRole", normalizeResourceName(service.Name)) - template.Resources[role] = &iam.Role{ - AssumeRolePolicyDocument: ausocalingAssumeRolePolicyDocument, - Path: "/", - Policies: []iam.Role_Policy{ - { - PolicyDocument: &PolicyDocument{ - Statement: []PolicyStatement{ - { - Effect: "Allow", - Action: []string{ - actionAutoScaling, - actionDescribeService, - actionUpdateService, - actionGetMetrics, - }, - Resource: []string{cloudformation.Ref(serviceResourceName(service.Name))}, - }, - }, - }, - PolicyName: "service-autoscaling", - }, - }, - Tags: serviceTags(project, service), - } - - // Why isn't this just the service ARN ????? - resourceID := cloudformation.Join("/", []string{"service", resources.cluster.ID(), cloudformation.GetAtt(serviceResourceName(service.Name), "Name")}) - - target := fmt.Sprintf("%sScalableTarget", normalizeResourceName(service.Name)) - template.Resources[target] = &applicationautoscaling.ScalableTarget{ - MaxCapacity: config.Max, - MinCapacity: config.Min, - ResourceId: resourceID, - RoleARN: cloudformation.GetAtt(role, "Arn"), - ScalableDimension: applicationautoscaling2.ScalableDimensionEcsServiceDesiredCount, - ServiceNamespace: applicationautoscaling2.ServiceNamespaceEcs, - AWSCloudFormationDependsOn: []string{serviceResourceName(service.Name)}, - } - - var ( - metric = applicationautoscaling2.MetricTypeEcsserviceAverageCpuutilization - targetPercent = config.CPU - ) - if config.Memory != 0 { - metric = applicationautoscaling2.MetricTypeEcsserviceAverageMemoryUtilization - targetPercent = config.Memory - } - - policy := fmt.Sprintf("%sScalingPolicy", normalizeResourceName(service.Name)) - template.Resources[policy] = &applicationautoscaling.ScalingPolicy{ - PolicyType: "TargetTrackingScaling", - PolicyName: policy, - ScalingTargetId: cloudformation.Ref(target), - StepScalingPolicyConfiguration: nil, - TargetTrackingScalingPolicyConfiguration: &applicationautoscaling.ScalingPolicy_TargetTrackingScalingPolicyConfiguration{ - PredefinedMetricSpecification: &applicationautoscaling.ScalingPolicy_PredefinedMetricSpecification{ - PredefinedMetricType: metric, - }, - ScaleOutCooldown: 60, - ScaleInCooldown: 60, - TargetValue: float64(targetPercent), - }, - } - return nil -} diff --git a/ecs/autoscaling_test.go b/ecs/autoscaling_test.go deleted file mode 100644 index 5e94f8ee3..000000000 --- a/ecs/autoscaling_test.go +++ /dev/null @@ -1,43 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "testing" - - autoscaling "github.com/awslabs/goformation/v4/cloudformation/applicationautoscaling" - "gotest.tools/v3/assert" -) - -func TestAutoScaling(t *testing.T) { - template := convertYaml(t, ` -services: - foo: - image: hello_world - deploy: - x-aws-autoscaling: - cpu: 75 - max: 10 -`, useDefaultVPC) - target := template.Resources["FooScalableTarget"].(*autoscaling.ScalableTarget) - assert.Check(t, target != nil) //nolint:staticcheck - assert.Check(t, target.MaxCapacity == 10) //nolint:staticcheck - - policy := template.Resources["FooScalingPolicy"].(*autoscaling.ScalingPolicy) - assert.Check(t, policy != nil) //nolint:staticcheck - assert.Check(t, policy.TargetTrackingScalingPolicyConfiguration.TargetValue == float64(75)) //nolint:staticcheck -} diff --git a/ecs/aws.go b/ecs/aws.go deleted file mode 100644 index 45b79ba08..000000000 --- a/ecs/aws.go +++ /dev/null @@ -1,82 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - - "github.com/aws/aws-sdk-go/service/cloudformation" - "github.com/aws/aws-sdk-go/service/ecs" - - "github.com/docker/compose-cli/api/secrets" - "github.com/docker/compose-cli/pkg/api" -) - -const ( - awsTypeCapacityProvider = "AWS::ECS::CapacityProvider" - awsTypeAutoscalingGroup = "AWS::AutoScaling::AutoScalingGroup" -) - -//go:generate mockgen -destination=./aws_mock.go -self_package "github.com/docker/compose-cli/ecs" -package=ecs . API - -// API hides aws-go-sdk into a simpler, focussed API subset -type API interface { - CheckRequirements(ctx context.Context, region string) error - ResolveCluster(ctx context.Context, nameOrArn string) (awsResource, error) - CreateCluster(ctx context.Context, name string) (string, error) - CheckVPC(ctx context.Context, vpcID string) error - GetDefaultVPC(ctx context.Context) (string, error) - GetSubNets(ctx context.Context, vpcID string) ([]awsResource, error) - IsPublicSubnet(ctx context.Context, subNetID string) (bool, error) - GetRoleArn(ctx context.Context, name string) (string, error) - StackExists(ctx context.Context, name string) (bool, error) - CreateStack(ctx context.Context, name string, region string, template []byte) error - CreateChangeSet(ctx context.Context, name string, region string, template []byte) (string, error) - UpdateStack(ctx context.Context, changeset string) error - WaitStackComplete(ctx context.Context, name string, operation int) error - GetStackID(ctx context.Context, name string) (string, error) - ListStacks(ctx context.Context) ([]api.Stack, error) - GetStackClusterID(ctx context.Context, stack string) (string, error) - GetServiceTaskDefinition(ctx context.Context, cluster string, serviceArns []string) (map[string]string, error) - ListStackServices(ctx context.Context, stack string) ([]string, error) - GetServiceTasks(ctx context.Context, cluster string, service string, stopped bool) ([]*ecs.Task, error) - GetTaskStoppedReason(ctx context.Context, cluster string, taskArn string) (string, error) - DescribeStackEvents(ctx context.Context, stackID string) ([]*cloudformation.StackEvent, error) - ListStackParameters(ctx context.Context, name string) (map[string]string, error) - ListStackResources(ctx context.Context, name string) (stackResources, error) - DeleteStack(ctx context.Context, name string) error - CreateSecret(ctx context.Context, secret secrets.Secret) (string, error) - InspectSecret(ctx context.Context, id string) (secrets.Secret, error) - ListSecrets(ctx context.Context) ([]secrets.Secret, error) - DeleteSecret(ctx context.Context, id string, recover bool) error - GetLogs(ctx context.Context, name string, consumer func(container string, service string, message string), follow bool) error - DescribeService(ctx context.Context, cluster string, arn string) (api.ServiceStatus, error) - DescribeServiceTasks(ctx context.Context, cluster string, project string, service string) ([]api.ContainerSummary, error) - getURLWithPortMapping(ctx context.Context, targetGroupArns []string) ([]api.PortPublisher, error) - ListTasks(ctx context.Context, cluster string, family string) ([]string, error) - GetPublicIPs(ctx context.Context, interfaces ...string) (map[string]string, error) - ResolveLoadBalancer(ctx context.Context, nameOrArn string) (awsResource, string, string, []awsResource, error) - GetLoadBalancerURL(ctx context.Context, arn string) (string, error) - GetParameter(ctx context.Context, name string) (string, error) - SecurityGroupExists(ctx context.Context, sg string) (bool, error) - DeleteCapacityProvider(ctx context.Context, arn string) error - DeleteAutoscalingGroup(ctx context.Context, arn string) error - ResolveFileSystem(ctx context.Context, id string) (awsResource, error) - ListFileSystems(ctx context.Context, tags map[string]string) ([]awsResource, error) - CreateFileSystem(ctx context.Context, tags map[string]string, options VolumeCreateOptions) (awsResource, error) - DeleteFileSystem(ctx context.Context, id string) error -} diff --git a/ecs/awsResources.go b/ecs/awsResources.go deleted file mode 100644 index dfb0e4c1b..000000000 --- a/ecs/awsResources.go +++ /dev/null @@ -1,518 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - "fmt" - "strconv" - "strings" - - "github.com/aws/aws-sdk-go/aws/arn" - "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/awslabs/goformation/v4/cloudformation" - "github.com/awslabs/goformation/v4/cloudformation/ec2" - "github.com/awslabs/goformation/v4/cloudformation/ecs" - "github.com/awslabs/goformation/v4/cloudformation/efs" - "github.com/awslabs/goformation/v4/cloudformation/elasticloadbalancingv2" - "github.com/compose-spec/compose-go/types" - "github.com/docker/compose-cli/pkg/api" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -// awsResources hold the AWS component being used or created to support services definition -type awsResources struct { - vpc string // shouldn't this also be an awsResource ? - subnets []awsResource - cluster awsResource - loadBalancer awsResource - loadBalancerType string - securityGroups map[string]string - filesystems map[string]awsResource -} - -func (r *awsResources) serviceSecurityGroups(service types.ServiceConfig) []string { - var groups []string - for net := range service.Networks { - groups = append(groups, r.securityGroups[net]) - } - return groups -} - -func (r *awsResources) allSecurityGroups() []string { - var securityGroups []string - for _, r := range r.securityGroups { - securityGroups = append(securityGroups, r) - } - return securityGroups -} - -func (r *awsResources) subnetsIDs() []string { - var ids []string - for _, r := range r.subnets { - ids = append(ids, r.ID()) - } - return ids -} - -// awsResource is abstract representation for any (existing or future) AWS resource that we can refer both by ID or full ARN -type awsResource interface { - ARN() string - ID() string -} - -// existingAWSResource hold references to an existing AWS component -type existingAWSResource struct { - arn string - id string -} - -func (r existingAWSResource) ARN() string { - return r.arn -} - -func (r existingAWSResource) ID() string { - return r.id -} - -// cloudformationResource hold references to a future AWS resource managed by CloudFormation -// to be used by CloudFormation resources where Ref returns the Amazon Resource ID -type cloudformationResource struct { - logicalName string -} - -func (r cloudformationResource) ARN() string { - return cloudformation.GetAtt(r.logicalName, "Arn") -} - -func (r cloudformationResource) ID() string { - return cloudformation.Ref(r.logicalName) -} - -// cloudformationARNResource hold references to a future AWS resource managed by CloudFormation -// to be used by CloudFormation resources where Ref returns the Amazon Resource Name (ARN) -type cloudformationARNResource struct { - logicalName string - nameProperty string -} - -func (r cloudformationARNResource) ARN() string { - return cloudformation.Ref(r.logicalName) -} - -func (r cloudformationARNResource) ID() string { - return cloudformation.GetAtt(r.logicalName, r.nameProperty) -} - -// parse look into compose project for configured resource to use, and check they are valid -func (b *ecsAPIService) parse(ctx context.Context, project *types.Project, template *cloudformation.Template) (awsResources, error) { - r := awsResources{} - var err error - r.cluster, err = b.parseClusterExtension(ctx, project, template) - if err != nil { - return r, err - } - err = b.parseLoadBalancerExtension(ctx, project, &r) - if err != nil { - return r, err - } - err = b.parseVPCExtension(ctx, project, &r) - if err != nil { - return r, err - } - r.securityGroups, err = b.parseExternalNetworks(ctx, project) - if err != nil { - return r, err - } - r.filesystems, err = b.parseExternalVolumes(ctx, project) - if err != nil { - return r, err - } - return r, nil -} - -func (b *ecsAPIService) parseClusterExtension(ctx context.Context, project *types.Project, template *cloudformation.Template) (awsResource, error) { - if x, ok := project.Extensions[extensionCluster]; ok { - nameOrArn := x.(string) // can be name _or_ ARN. - cluster, err := b.aws.ResolveCluster(ctx, nameOrArn) - if err != nil { - return nil, err - } - if !ok { - return nil, errors.Wrapf(api.ErrNotFound, "cluster %q does not exist", cluster) - } - - template.Metadata["Cluster"] = cluster.ARN() - return cluster, nil - } - return nil, nil -} - -func (b *ecsAPIService) parseVPCExtension(ctx context.Context, project *types.Project, r *awsResources) error { - var vpc string - if x, ok := project.Extensions[extensionVPC]; ok { - vpc = x.(string) - ARN, err := arn.Parse(vpc) - if err == nil { - // User has set an ARN, like the one Terraform shows as output, while we expect an ID - id := ARN.Resource - i := strings.LastIndex(id, "/") - vpc = id[i+1:] - } - - if r.vpc != "" { - if r.vpc != vpc { - return fmt.Errorf("load balancer set by %s is attached to VPC %s", extensionLoadBalancer, r.vpc) - } - return nil - } - - err = b.aws.CheckVPC(ctx, vpc) - if err != nil { - return err - } - - } else { - if r.vpc != "" { - return nil - } - - defaultVPC, err := b.aws.GetDefaultVPC(ctx) - if err != nil { - return err - } - vpc = defaultVPC - } - - subNets, err := b.aws.GetSubNets(ctx, vpc) - if err != nil { - return err - } - - var publicSubNets []awsResource - for _, subNet := range subNets { - isPublic, err := b.aws.IsPublicSubnet(ctx, subNet.ID()) - if err != nil { - return err - } - if isPublic { - publicSubNets = append(publicSubNets, subNet) - } - } - - if len(publicSubNets) < 2 { - return fmt.Errorf("VPC %s should have at least 2 associated public subnets in different availability zones", vpc) - } - - r.vpc = vpc - r.subnets = subNets - return nil -} - -func (b *ecsAPIService) parseLoadBalancerExtension(ctx context.Context, project *types.Project, r *awsResources) error { - if x, ok := project.Extensions[extensionLoadBalancer]; ok { - nameOrArn := x.(string) - loadBalancer, loadBalancerType, vpc, subnets, err := b.aws.ResolveLoadBalancer(ctx, nameOrArn) - if err != nil { - return err - } - - required := getRequiredLoadBalancerType(project) - if loadBalancerType != required { - return fmt.Errorf("load balancer %q is of type %s, project require a %s", nameOrArn, loadBalancerType, required) - } - - r.loadBalancer = loadBalancer - r.loadBalancerType = loadBalancerType - r.vpc = vpc - r.subnets = subnets - return err - } - return nil -} - -func (b *ecsAPIService) parseExternalNetworks(ctx context.Context, project *types.Project) (map[string]string, error) { - securityGroups := make(map[string]string, len(project.Networks)) - for name, net := range project.Networks { - // FIXME remove this for G.A - if x, ok := net.Extensions[extensionSecurityGroup]; ok { - logrus.Warn("to use an existing security-group, use `network.external` and `network.name` in your compose file") - logrus.Debugf("Security Group for network %q set by user to %q", net.Name, x) - net.External.External = true - net.Name = x.(string) - project.Networks[name] = net - } - - if !net.External.External { - continue - } - exists, err := b.aws.SecurityGroupExists(ctx, net.Name) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.Wrapf(api.ErrNotFound, "security group %q doesn't exist", net.Name) - } - securityGroups[name] = net.Name - } - return securityGroups, nil -} - -func (b *ecsAPIService) parseExternalVolumes(ctx context.Context, project *types.Project) (map[string]awsResource, error) { - filesystems := make(map[string]awsResource, len(project.Volumes)) - for name, vol := range project.Volumes { - if vol.External.External { - arn, err := b.aws.ResolveFileSystem(ctx, vol.Name) - if err != nil { - return nil, err - } - filesystems[name] = arn - continue - } - - logrus.Debugf("searching for existing filesystem as volume %q", name) - tags := map[string]string{ - api.ProjectLabel: project.Name, - api.VolumeLabel: name, - } - previous, err := b.aws.ListFileSystems(ctx, tags) - if err != nil { - return nil, err - } - - if len(previous) > 1 { - return nil, fmt.Errorf("multiple filesystems are tags as project=%q, volume=%q", project.Name, name) - } - if len(previous) == 1 { - filesystems[name] = previous[0] - } - } - return filesystems, nil -} - -// ensureResources create required resources in template if not yet defined -func (b *ecsAPIService) ensureResources(resources *awsResources, project *types.Project, template *cloudformation.Template) error { - b.ensureCluster(resources, project, template) - b.ensureNetworks(resources, project, template) - err := b.ensureVolumes(resources, project, template) - if err != nil { - return err - } - b.ensureLoadBalancer(resources, project, template) - return nil -} - -func (b *ecsAPIService) ensureCluster(r *awsResources, project *types.Project, template *cloudformation.Template) { - if r.cluster != nil { - return - } - template.Resources["Cluster"] = &ecs.Cluster{ - ClusterName: project.Name, - Tags: projectTags(project), - } - r.cluster = cloudformationResource{logicalName: "Cluster"} -} - -func (b *ecsAPIService) ensureNetworks(r *awsResources, project *types.Project, template *cloudformation.Template) { - if r.securityGroups == nil { - r.securityGroups = make(map[string]string, len(project.Networks)) - } - for name, net := range project.Networks { - if _, ok := r.securityGroups[name]; ok { - continue - } - securityGroup := networkResourceName(name) - template.Resources[securityGroup] = &ec2.SecurityGroup{ - GroupDescription: fmt.Sprintf("%s Security Group for %s network", project.Name, name), - VpcId: r.vpc, - Tags: networkTags(project, net), - } - - ingress := securityGroup + "Ingress" - template.Resources[ingress] = &ec2.SecurityGroupIngress{ - Description: fmt.Sprintf("Allow communication within network %s", name), - IpProtocol: allProtocols, - GroupId: cloudformation.Ref(securityGroup), - SourceSecurityGroupId: cloudformation.Ref(securityGroup), - } - - r.securityGroups[name] = cloudformation.Ref(securityGroup) - } -} - -func (b *ecsAPIService) ensureVolumes(r *awsResources, project *types.Project, template *cloudformation.Template) error { - for name, volume := range project.Volumes { - if _, ok := r.filesystems[name]; ok { - continue - } - - var backupPolicy *efs.FileSystem_BackupPolicy - if backup, ok := volume.DriverOpts["backup_policy"]; ok { - backupPolicy = &efs.FileSystem_BackupPolicy{ - Status: backup, - } - } - - var lifecyclePolicies []efs.FileSystem_LifecyclePolicy - if policy, ok := volume.DriverOpts["lifecycle_policy"]; ok { - lifecyclePolicies = append(lifecyclePolicies, efs.FileSystem_LifecyclePolicy{ - TransitionToIA: strings.TrimSpace(policy), - }) - } - - var provisionedThroughputInMibps float64 - if t, ok := volume.DriverOpts["provisioned_throughput"]; ok { - v, err := strconv.ParseFloat(t, 64) - if err != nil { - return err - } - provisionedThroughputInMibps = v - } - - var performanceMode = volume.DriverOpts["performance_mode"] - var throughputMode = volume.DriverOpts["throughput_mode"] - var kmsKeyID = volume.DriverOpts["kms_key_id"] - - n := volumeResourceName(name) - template.Resources[n] = &efs.FileSystem{ - BackupPolicy: backupPolicy, - Encrypted: true, - FileSystemPolicy: nil, - FileSystemTags: []efs.FileSystem_ElasticFileSystemTag{ - { - Key: api.ProjectLabel, - Value: project.Name, - }, - { - Key: api.VolumeLabel, - Value: name, - }, - { - Key: "Name", - Value: volume.Name, - }, - }, - KmsKeyId: kmsKeyID, - LifecyclePolicies: lifecyclePolicies, - PerformanceMode: performanceMode, - ProvisionedThroughputInMibps: provisionedThroughputInMibps, - ThroughputMode: throughputMode, - AWSCloudFormationDeletionPolicy: "Retain", - } - r.filesystems[name] = cloudformationResource{logicalName: n} - } - return nil -} - -func (b *ecsAPIService) ensureLoadBalancer(r *awsResources, project *types.Project, template *cloudformation.Template) { - if r.loadBalancer != nil { - return - } - if allServices(project.Services, func(it types.ServiceConfig) bool { - return len(it.Ports) == 0 - }) { - logrus.Debug("Application does not expose any public port, so no need for a LoadBalancer") - return - } - - balancerType := getRequiredLoadBalancerType(project) - var securityGroups []string - if balancerType == elbv2.LoadBalancerTypeEnumApplication { - // see https://docs.aws.amazon.com/elasticloadbalancing/latest/network/target-group-register-targets.html#target-security-groups - // Network Load Balancers do not have associated security groups - securityGroups = r.getLoadBalancerSecurityGroups(project) - } - - var loadBalancerAttributes []elasticloadbalancingv2.LoadBalancer_LoadBalancerAttribute - if balancerType == elbv2.LoadBalancerTypeEnumNetwork { - loadBalancerAttributes = append( - loadBalancerAttributes, - elasticloadbalancingv2.LoadBalancer_LoadBalancerAttribute{ - Key: "load_balancing.cross_zone.enabled", - Value: "true", - }) - } - - template.Resources["LoadBalancer"] = &elasticloadbalancingv2.LoadBalancer{ - Scheme: elbv2.LoadBalancerSchemeEnumInternetFacing, - SecurityGroups: securityGroups, - Subnets: r.subnetsIDs(), - Tags: projectTags(project), - Type: balancerType, - LoadBalancerAttributes: loadBalancerAttributes, - } - r.loadBalancer = cloudformationARNResource{ - logicalName: "LoadBalancer", - nameProperty: "LoadBalancerName", - } - r.loadBalancerType = balancerType -} - -func (r *awsResources) getLoadBalancerSecurityGroups(project *types.Project) []string { - securityGroups := []string{} - for name, network := range project.Networks { - if !network.Internal { - securityGroups = append(securityGroups, r.securityGroups[name]) - } - } - return securityGroups -} - -func getRequiredLoadBalancerType(project *types.Project) string { - loadBalancerType := elbv2.LoadBalancerTypeEnumNetwork - if allServices(project.Services, func(it types.ServiceConfig) bool { - return allPorts(it.Ports, portIsHTTP) - }) { - loadBalancerType = elbv2.LoadBalancerTypeEnumApplication - } - return loadBalancerType -} - -func portIsHTTP(it types.ServicePortConfig) bool { - if v, ok := it.Extensions[extensionProtocol]; ok { - protocol := v.(string) - return protocol == "http" || protocol == "https" - } - return it.Target == 80 || it.Target == 443 -} - -// predicate[types.ServiceConfig] -type servicePredicate func(it types.ServiceConfig) bool - -// all[types.ServiceConfig] -func allServices(services types.Services, p servicePredicate) bool { - for _, s := range services { - if !p(s) { - return false - } - } - return true -} - -// predicate[types.ServicePortConfig] -type portPredicate func(it types.ServicePortConfig) bool - -// all[types.ServicePortConfig] -func allPorts(ports []types.ServicePortConfig, p portPredicate) bool { - for _, s := range ports { - if !p(s) { - return false - } - } - return true -} diff --git a/ecs/aws_mock.go b/ecs/aws_mock.go deleted file mode 100644 index 3e16ae51f..000000000 --- a/ecs/aws_mock.go +++ /dev/null @@ -1,695 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Container: github.com/docker/compose-cli/ecs (interfaces: API) - -// Package ecs is a generated GoMock package. -package ecs - -import ( - context "context" - cloudformation "github.com/aws/aws-sdk-go/service/cloudformation" - ecs "github.com/aws/aws-sdk-go/service/ecs" - secrets "github.com/docker/compose-cli/api/secrets" - compose "github.com/docker/compose-cli/pkg/api" - gomock "github.com/golang/mock/gomock" - reflect "reflect" -) - -// MockAPI is a mock of API interface -type MockAPI struct { - ctrl *gomock.Controller - recorder *MockAPIMockRecorder -} - -// MockAPIMockRecorder is the mock recorder for MockAPI -type MockAPIMockRecorder struct { - mock *MockAPI -} - -// NewMockAPI creates a new mock instance -func NewMockAPI(ctrl *gomock.Controller) *MockAPI { - mock := &MockAPI{ctrl: ctrl} - mock.recorder = &MockAPIMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use -func (m *MockAPI) EXPECT() *MockAPIMockRecorder { - return m.recorder -} - -// CheckRequirements mocks base method -func (m *MockAPI) CheckRequirements(arg0 context.Context, arg1 string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CheckRequirements", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// CheckRequirements indicates an expected call of CheckRequirements -func (mr *MockAPIMockRecorder) CheckRequirements(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckRequirements", reflect.TypeOf((*MockAPI)(nil).CheckRequirements), arg0, arg1) -} - -// CheckVPC mocks base method -func (m *MockAPI) CheckVPC(arg0 context.Context, arg1 string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CheckVPC", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// CheckVPC indicates an expected call of CheckVPC -func (mr *MockAPIMockRecorder) CheckVPC(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckVPC", reflect.TypeOf((*MockAPI)(nil).CheckVPC), arg0, arg1) -} - -// CreateChangeSet mocks base method -func (m *MockAPI) CreateChangeSet(arg0 context.Context, arg1, arg2 string, arg3 []byte) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateChangeSet", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateChangeSet indicates an expected call of CreateChangeSet -func (mr *MockAPIMockRecorder) CreateChangeSet(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateChangeSet", reflect.TypeOf((*MockAPI)(nil).CreateChangeSet), arg0, arg1, arg2, arg3) -} - -// CreateCluster mocks base method -func (m *MockAPI) CreateCluster(arg0 context.Context, arg1 string) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateCluster", arg0, arg1) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateCluster indicates an expected call of CreateCluster -func (mr *MockAPIMockRecorder) CreateCluster(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateCluster", reflect.TypeOf((*MockAPI)(nil).CreateCluster), arg0, arg1) -} - -// CreateFileSystem mocks base method -func (m *MockAPI) CreateFileSystem(arg0 context.Context, arg1 map[string]string, arg2 VolumeCreateOptions) (awsResource, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateFileSystem", arg0, arg1, arg2) - ret0, _ := ret[0].(awsResource) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateFileSystem indicates an expected call of CreateFileSystem -func (mr *MockAPIMockRecorder) CreateFileSystem(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFileSystem", reflect.TypeOf((*MockAPI)(nil).CreateFileSystem), arg0, arg1, arg2) -} - -// CreateSecret mocks base method -func (m *MockAPI) CreateSecret(arg0 context.Context, arg1 secrets.Secret) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateSecret", arg0, arg1) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateSecret indicates an expected call of CreateSecret -func (mr *MockAPIMockRecorder) CreateSecret(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSecret", reflect.TypeOf((*MockAPI)(nil).CreateSecret), arg0, arg1) -} - -// CreateStack mocks base method -func (m *MockAPI) CreateStack(arg0 context.Context, arg1, arg2 string, arg3 []byte) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateStack", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(error) - return ret0 -} - -// CreateStack indicates an expected call of CreateStack -func (mr *MockAPIMockRecorder) CreateStack(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateStack", reflect.TypeOf((*MockAPI)(nil).CreateStack), arg0, arg1, arg2, arg3) -} - -// DeleteAutoscalingGroup mocks base method -func (m *MockAPI) DeleteAutoscalingGroup(arg0 context.Context, arg1 string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteAutoscalingGroup", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// DeleteAutoscalingGroup indicates an expected call of DeleteAutoscalingGroup -func (mr *MockAPIMockRecorder) DeleteAutoscalingGroup(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAutoscalingGroup", reflect.TypeOf((*MockAPI)(nil).DeleteAutoscalingGroup), arg0, arg1) -} - -// DeleteCapacityProvider mocks base method -func (m *MockAPI) DeleteCapacityProvider(arg0 context.Context, arg1 string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteCapacityProvider", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// DeleteCapacityProvider indicates an expected call of DeleteCapacityProvider -func (mr *MockAPIMockRecorder) DeleteCapacityProvider(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCapacityProvider", reflect.TypeOf((*MockAPI)(nil).DeleteCapacityProvider), arg0, arg1) -} - -// DeleteFileSystem mocks base method -func (m *MockAPI) DeleteFileSystem(arg0 context.Context, arg1 string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteFileSystem", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// DeleteFileSystem indicates an expected call of DeleteFileSystem -func (mr *MockAPIMockRecorder) DeleteFileSystem(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFileSystem", reflect.TypeOf((*MockAPI)(nil).DeleteFileSystem), arg0, arg1) -} - -// DeleteSecret mocks base method -func (m *MockAPI) DeleteSecret(arg0 context.Context, arg1 string, arg2 bool) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteSecret", arg0, arg1, arg2) - ret0, _ := ret[0].(error) - return ret0 -} - -// DeleteSecret indicates an expected call of DeleteSecret -func (mr *MockAPIMockRecorder) DeleteSecret(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSecret", reflect.TypeOf((*MockAPI)(nil).DeleteSecret), arg0, arg1, arg2) -} - -// DeleteStack mocks base method -func (m *MockAPI) DeleteStack(arg0 context.Context, arg1 string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteStack", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// DeleteStack indicates an expected call of DeleteStack -func (mr *MockAPIMockRecorder) DeleteStack(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteStack", reflect.TypeOf((*MockAPI)(nil).DeleteStack), arg0, arg1) -} - -// DescribeService mocks base method -func (m *MockAPI) DescribeService(arg0 context.Context, arg1, arg2 string) (compose.ServiceStatus, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DescribeService", arg0, arg1, arg2) - ret0, _ := ret[0].(compose.ServiceStatus) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// DescribeService indicates an expected call of DescribeService -func (mr *MockAPIMockRecorder) DescribeService(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeService", reflect.TypeOf((*MockAPI)(nil).DescribeService), arg0, arg1, arg2) -} - -// DescribeServiceTasks mocks base method -func (m *MockAPI) DescribeServiceTasks(arg0 context.Context, arg1, arg2, arg3 string) ([]compose.ContainerSummary, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DescribeServiceTasks", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].([]compose.ContainerSummary) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// DescribeServiceTasks indicates an expected call of DescribeServiceTasks -func (mr *MockAPIMockRecorder) DescribeServiceTasks(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeServiceTasks", reflect.TypeOf((*MockAPI)(nil).DescribeServiceTasks), arg0, arg1, arg2, arg3) -} - -// DescribeStackEvents mocks base method -func (m *MockAPI) DescribeStackEvents(arg0 context.Context, arg1 string) ([]*cloudformation.StackEvent, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DescribeStackEvents", arg0, arg1) - ret0, _ := ret[0].([]*cloudformation.StackEvent) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// DescribeStackEvents indicates an expected call of DescribeStackEvents -func (mr *MockAPIMockRecorder) DescribeStackEvents(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeStackEvents", reflect.TypeOf((*MockAPI)(nil).DescribeStackEvents), arg0, arg1) -} - -// GetDefaultVPC mocks base method -func (m *MockAPI) GetDefaultVPC(arg0 context.Context) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetDefaultVPC", arg0) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetDefaultVPC indicates an expected call of GetDefaultVPC -func (mr *MockAPIMockRecorder) GetDefaultVPC(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultVPC", reflect.TypeOf((*MockAPI)(nil).GetDefaultVPC), arg0) -} - -// GetLoadBalancerURL mocks base method -func (m *MockAPI) GetLoadBalancerURL(arg0 context.Context, arg1 string) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLoadBalancerURL", arg0, arg1) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLoadBalancerURL indicates an expected call of GetLoadBalancerURL -func (mr *MockAPIMockRecorder) GetLoadBalancerURL(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLoadBalancerURL", reflect.TypeOf((*MockAPI)(nil).GetLoadBalancerURL), arg0, arg1) -} - -// GetLogs mocks base method -func (m *MockAPI) GetLogs(arg0 context.Context, arg1 string, arg2 func(string, string, string), arg3 bool) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLogs", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(error) - return ret0 -} - -// GetLogs indicates an expected call of GetLogs -func (mr *MockAPIMockRecorder) GetLogs(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogs", reflect.TypeOf((*MockAPI)(nil).GetLogs), arg0, arg1, arg2, arg3) -} - -// GetParameter mocks base method -func (m *MockAPI) GetParameter(arg0 context.Context, arg1 string) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetParameter", arg0, arg1) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetParameter indicates an expected call of GetParameter -func (mr *MockAPIMockRecorder) GetParameter(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetParameter", reflect.TypeOf((*MockAPI)(nil).GetParameter), arg0, arg1) -} - -// GetPublicIPs mocks base method -func (m *MockAPI) GetPublicIPs(arg0 context.Context, arg1 ...string) (map[string]string, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0} - for _, a := range arg1 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetPublicIPs", varargs...) - ret0, _ := ret[0].(map[string]string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetPublicIPs indicates an expected call of GetPublicIPs -func (mr *MockAPIMockRecorder) GetPublicIPs(arg0 interface{}, arg1 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0}, arg1...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublicIPs", reflect.TypeOf((*MockAPI)(nil).GetPublicIPs), varargs...) -} - -// GetRoleArn mocks base method -func (m *MockAPI) GetRoleArn(arg0 context.Context, arg1 string) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetRoleArn", arg0, arg1) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetRoleArn indicates an expected call of GetRoleArn -func (mr *MockAPIMockRecorder) GetRoleArn(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRoleArn", reflect.TypeOf((*MockAPI)(nil).GetRoleArn), arg0, arg1) -} - -// GetServiceTaskDefinition mocks base method -func (m *MockAPI) GetServiceTaskDefinition(arg0 context.Context, arg1 string, arg2 []string) (map[string]string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetServiceTaskDefinition", arg0, arg1, arg2) - ret0, _ := ret[0].(map[string]string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetServiceTaskDefinition indicates an expected call of GetServiceTaskDefinition -func (mr *MockAPIMockRecorder) GetServiceTaskDefinition(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceTaskDefinition", reflect.TypeOf((*MockAPI)(nil).GetServiceTaskDefinition), arg0, arg1, arg2) -} - -// GetServiceTasks mocks base method -func (m *MockAPI) GetServiceTasks(arg0 context.Context, arg1, arg2 string, arg3 bool) ([]*ecs.Task, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetServiceTasks", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].([]*ecs.Task) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetServiceTasks indicates an expected call of GetServiceTasks -func (mr *MockAPIMockRecorder) GetServiceTasks(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceTasks", reflect.TypeOf((*MockAPI)(nil).GetServiceTasks), arg0, arg1, arg2, arg3) -} - -// GetStackClusterID mocks base method -func (m *MockAPI) GetStackClusterID(arg0 context.Context, arg1 string) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetStackClusterID", arg0, arg1) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetStackClusterID indicates an expected call of GetStackClusterID -func (mr *MockAPIMockRecorder) GetStackClusterID(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStackClusterID", reflect.TypeOf((*MockAPI)(nil).GetStackClusterID), arg0, arg1) -} - -// GetStackID mocks base method -func (m *MockAPI) GetStackID(arg0 context.Context, arg1 string) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetStackID", arg0, arg1) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetStackID indicates an expected call of GetStackID -func (mr *MockAPIMockRecorder) GetStackID(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStackID", reflect.TypeOf((*MockAPI)(nil).GetStackID), arg0, arg1) -} - -// GetSubNets mocks base method -func (m *MockAPI) GetSubNets(arg0 context.Context, arg1 string) ([]awsResource, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetSubNets", arg0, arg1) - ret0, _ := ret[0].([]awsResource) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetSubNets indicates an expected call of GetSubNets -func (mr *MockAPIMockRecorder) GetSubNets(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubNets", reflect.TypeOf((*MockAPI)(nil).GetSubNets), arg0, arg1) -} - -// GetTaskStoppedReason mocks base method -func (m *MockAPI) GetTaskStoppedReason(arg0 context.Context, arg1, arg2 string) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetTaskStoppedReason", arg0, arg1, arg2) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetTaskStoppedReason indicates an expected call of GetTaskStoppedReason -func (mr *MockAPIMockRecorder) GetTaskStoppedReason(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTaskStoppedReason", reflect.TypeOf((*MockAPI)(nil).GetTaskStoppedReason), arg0, arg1, arg2) -} - -// InspectSecret mocks base method -func (m *MockAPI) InspectSecret(arg0 context.Context, arg1 string) (secrets.Secret, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "InspectSecret", arg0, arg1) - ret0, _ := ret[0].(secrets.Secret) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// InspectSecret indicates an expected call of InspectSecret -func (mr *MockAPIMockRecorder) InspectSecret(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InspectSecret", reflect.TypeOf((*MockAPI)(nil).InspectSecret), arg0, arg1) -} - -// IsPublicSubnet mocks base method -func (m *MockAPI) IsPublicSubnet(arg0 context.Context, arg1 string) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IsPublicSubnet", arg0, arg1) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// IsPublicSubnet indicates an expected call of IsPublicSubnet -func (mr *MockAPIMockRecorder) IsPublicSubnet(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsPublicSubnet", reflect.TypeOf((*MockAPI)(nil).IsPublicSubnet), arg0, arg1) -} - -// ListFileSystems mocks base method -func (m *MockAPI) ListFileSystems(arg0 context.Context, arg1 map[string]string) ([]awsResource, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListFileSystems", arg0, arg1) - ret0, _ := ret[0].([]awsResource) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListFileSystems indicates an expected call of ListFileSystems -func (mr *MockAPIMockRecorder) ListFileSystems(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListFileSystems", reflect.TypeOf((*MockAPI)(nil).ListFileSystems), arg0, arg1) -} - -// ListSecrets mocks base method -func (m *MockAPI) ListSecrets(arg0 context.Context) ([]secrets.Secret, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListSecrets", arg0) - ret0, _ := ret[0].([]secrets.Secret) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListSecrets indicates an expected call of ListSecrets -func (mr *MockAPIMockRecorder) ListSecrets(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListSecrets", reflect.TypeOf((*MockAPI)(nil).ListSecrets), arg0) -} - -// ListStackParameters mocks base method -func (m *MockAPI) ListStackParameters(arg0 context.Context, arg1 string) (map[string]string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListStackParameters", arg0, arg1) - ret0, _ := ret[0].(map[string]string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListStackParameters indicates an expected call of ListStackParameters -func (mr *MockAPIMockRecorder) ListStackParameters(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListStackParameters", reflect.TypeOf((*MockAPI)(nil).ListStackParameters), arg0, arg1) -} - -// ListStackResources mocks base method -func (m *MockAPI) ListStackResources(arg0 context.Context, arg1 string) (stackResources, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListStackResources", arg0, arg1) - ret0, _ := ret[0].(stackResources) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListStackResources indicates an expected call of ListStackResources -func (mr *MockAPIMockRecorder) ListStackResources(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListStackResources", reflect.TypeOf((*MockAPI)(nil).ListStackResources), arg0, arg1) -} - -// ListStackServices mocks base method -func (m *MockAPI) ListStackServices(arg0 context.Context, arg1 string) ([]string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListStackServices", arg0, arg1) - ret0, _ := ret[0].([]string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListStackServices indicates an expected call of ListStackServices -func (mr *MockAPIMockRecorder) ListStackServices(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListStackServices", reflect.TypeOf((*MockAPI)(nil).ListStackServices), arg0, arg1) -} - -// ListStacks mocks base method -func (m *MockAPI) ListStacks(arg0 context.Context) ([]compose.Stack, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListStacks", arg0) - ret0, _ := ret[0].([]compose.Stack) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListStacks indicates an expected call of ListStacks -func (mr *MockAPIMockRecorder) ListStacks(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListStacks", reflect.TypeOf((*MockAPI)(nil).ListStacks), arg0) -} - -// ListTasks mocks base method -func (m *MockAPI) ListTasks(arg0 context.Context, arg1, arg2 string) ([]string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListTasks", arg0, arg1, arg2) - ret0, _ := ret[0].([]string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListTasks indicates an expected call of ListTasks -func (mr *MockAPIMockRecorder) ListTasks(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListTasks", reflect.TypeOf((*MockAPI)(nil).ListTasks), arg0, arg1, arg2) -} - -// ResolveCluster mocks base method -func (m *MockAPI) ResolveCluster(arg0 context.Context, arg1 string) (awsResource, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ResolveCluster", arg0, arg1) - ret0, _ := ret[0].(awsResource) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ResolveCluster indicates an expected call of ResolveCluster -func (mr *MockAPIMockRecorder) ResolveCluster(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResolveCluster", reflect.TypeOf((*MockAPI)(nil).ResolveCluster), arg0, arg1) -} - -// ResolveFileSystem mocks base method -func (m *MockAPI) ResolveFileSystem(arg0 context.Context, arg1 string) (awsResource, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ResolveFileSystem", arg0, arg1) - ret0, _ := ret[0].(awsResource) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ResolveFileSystem indicates an expected call of ResolveFileSystem -func (mr *MockAPIMockRecorder) ResolveFileSystem(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResolveFileSystem", reflect.TypeOf((*MockAPI)(nil).ResolveFileSystem), arg0, arg1) -} - -// ResolveLoadBalancer mocks base method -func (m *MockAPI) ResolveLoadBalancer(arg0 context.Context, arg1 string) (awsResource, string, string, []awsResource, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ResolveLoadBalancer", arg0, arg1) - ret0, _ := ret[0].(awsResource) - ret1, _ := ret[1].(string) - ret2, _ := ret[2].(string) - ret3, _ := ret[3].([]awsResource) - ret4, _ := ret[4].(error) - return ret0, ret1, ret2, ret3, ret4 -} - -// ResolveLoadBalancer indicates an expected call of ResolveLoadBalancer -func (mr *MockAPIMockRecorder) ResolveLoadBalancer(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResolveLoadBalancer", reflect.TypeOf((*MockAPI)(nil).ResolveLoadBalancer), arg0, arg1) -} - -// SecurityGroupExists mocks base method -func (m *MockAPI) SecurityGroupExists(arg0 context.Context, arg1 string) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SecurityGroupExists", arg0, arg1) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SecurityGroupExists indicates an expected call of SecurityGroupExists -func (mr *MockAPIMockRecorder) SecurityGroupExists(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SecurityGroupExists", reflect.TypeOf((*MockAPI)(nil).SecurityGroupExists), arg0, arg1) -} - -// StackExists mocks base method -func (m *MockAPI) StackExists(arg0 context.Context, arg1 string) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "StackExists", arg0, arg1) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// StackExists indicates an expected call of StackExists -func (mr *MockAPIMockRecorder) StackExists(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StackExists", reflect.TypeOf((*MockAPI)(nil).StackExists), arg0, arg1) -} - -// UpdateStack mocks base method -func (m *MockAPI) UpdateStack(arg0 context.Context, arg1 string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateStack", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// UpdateStack indicates an expected call of UpdateStack -func (mr *MockAPIMockRecorder) UpdateStack(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateStack", reflect.TypeOf((*MockAPI)(nil).UpdateStack), arg0, arg1) -} - -// WaitStackComplete mocks base method -func (m *MockAPI) WaitStackComplete(arg0 context.Context, arg1 string, arg2 int) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "WaitStackComplete", arg0, arg1, arg2) - ret0, _ := ret[0].(error) - return ret0 -} - -// WaitStackComplete indicates an expected call of WaitStackComplete -func (mr *MockAPIMockRecorder) WaitStackComplete(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitStackComplete", reflect.TypeOf((*MockAPI)(nil).WaitStackComplete), arg0, arg1, arg2) -} - -// getURLWithPortMapping mocks base method -func (m *MockAPI) getURLWithPortMapping(arg0 context.Context, arg1 []string) ([]compose.PortPublisher, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "getURLWithPortMapping", arg0, arg1) - ret0, _ := ret[0].([]compose.PortPublisher) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// getURLWithPortMapping indicates an expected call of getURLWithPortMapping -func (mr *MockAPIMockRecorder) getURLWithPortMapping(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getURLWithPortMapping", reflect.TypeOf((*MockAPI)(nil).getURLWithPortMapping), arg0, arg1) -} diff --git a/ecs/backend.go b/ecs/backend.go deleted file mode 100644 index 7fd06d0a6..000000000 --- a/ecs/backend.go +++ /dev/null @@ -1,162 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - "fmt" - - "github.com/docker/compose-cli/api/backend" - - "github.com/docker/compose-cli/api/cloud" - "github.com/docker/compose-cli/api/containers" - apicontext "github.com/docker/compose-cli/api/context" - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/api/resources" - "github.com/docker/compose-cli/api/secrets" - "github.com/docker/compose-cli/api/volumes" - "github.com/docker/compose-cli/pkg/api" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" -) - -const backendType = store.EcsContextType - -// ContextParams options for creating AWS context -type ContextParams struct { - Name string - Description string - AccessKey string - SecretKey string - Profile string - Region string - CredsFromEnv bool -} - -func (c ContextParams) haveRequiredEnvVars() bool { - if c.Profile != "" { - return true - } - if c.AccessKey != "" && c.SecretKey != "" { - return true - } - return false -} - -func init() { - backend.Register(backendType, backendType, service, getCloudService) -} - -func service() (backend.Service, error) { - contextStore := store.Instance() - currentContext := apicontext.Current() - var ecsContext store.EcsContext - - if err := contextStore.GetEndpoint(currentContext, &ecsContext); err != nil { - return nil, err - } - - return getEcsAPIService(ecsContext) -} - -func getEcsAPIService(ecsCtx store.EcsContext) (*ecsAPIService, error) { - region := "" - profile := ecsCtx.Profile - - if ecsCtx.CredentialsFromEnv { - env := getEnvVars() - if !env.haveRequiredEnvVars() { - return nil, fmt.Errorf("context requires credentials to be passed as environment variables") - } - profile = env.Profile - region = env.Region - } - - if region == "" { - r, err := getRegion(profile) - if err != nil { - return nil, err - } - region = r - } - - sess, err := session.NewSessionWithOptions(session.Options{ - Profile: profile, - SharedConfigState: session.SharedConfigEnable, - Config: aws.Config{ - Region: aws.String(region), - }, - }) - if err != nil { - return nil, err - } - - sdk := newSDK(sess) - return &ecsAPIService{ - ctx: ecsCtx, - Region: region, - aws: sdk, - }, nil -} - -type ecsAPIService struct { - ctx store.EcsContext - Region string - aws API -} - -func (b *ecsAPIService) ContainerService() containers.Service { - return nil -} - -func (b *ecsAPIService) ComposeService() api.Service { - return b -} - -func (b *ecsAPIService) SecretsService() secrets.Service { - return b -} - -func (b *ecsAPIService) VolumeService() volumes.Service { - return ecsVolumeService{backend: b} -} - -func (b *ecsAPIService) ResourceService() resources.Service { - return nil -} - -func getCloudService() (cloud.Service, error) { - return ecsCloudService{}, nil -} - -type ecsCloudService struct { -} - -func (a ecsCloudService) Login(ctx context.Context, params interface{}) error { - return api.ErrNotImplemented -} - -func (a ecsCloudService) Logout(ctx context.Context) error { - return api.ErrNotImplemented -} - -func (a ecsCloudService) CreateContextData(ctx context.Context, params interface{}) (interface{}, string, error) { - contextHelper := newContextCreateHelper() - createOpts := params.(ContextParams) - return contextHelper.createContextData(ctx, createOpts) -} diff --git a/ecs/cloudformation.go b/ecs/cloudformation.go deleted file mode 100644 index 8577fbd0d..000000000 --- a/ecs/cloudformation.go +++ /dev/null @@ -1,533 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - "fmt" - "io/ioutil" - "regexp" - "strings" - - ecsapi "github.com/aws/aws-sdk-go/service/ecs" - "github.com/aws/aws-sdk-go/service/elbv2" - cloudmapapi "github.com/aws/aws-sdk-go/service/servicediscovery" - "github.com/awslabs/goformation/v4/cloudformation" - "github.com/awslabs/goformation/v4/cloudformation/ec2" - "github.com/awslabs/goformation/v4/cloudformation/ecs" - "github.com/awslabs/goformation/v4/cloudformation/elasticloadbalancingv2" - "github.com/awslabs/goformation/v4/cloudformation/iam" - "github.com/awslabs/goformation/v4/cloudformation/logs" - "github.com/awslabs/goformation/v4/cloudformation/secretsmanager" - cloudmap "github.com/awslabs/goformation/v4/cloudformation/servicediscovery" - "github.com/cnabio/cnab-to-oci/remotes" - "github.com/compose-spec/compose-go/types" - "github.com/distribution/distribution/v3/reference" - cliconfig "github.com/docker/cli/cli/config" - "github.com/docker/compose-cli/api/config" - "github.com/docker/compose-cli/pkg/api" - "github.com/opencontainers/go-digest" - "sigs.k8s.io/kustomize/kyaml/yaml" - "sigs.k8s.io/kustomize/kyaml/yaml/merge2" -) - -func (b *ecsAPIService) Kill(ctx context.Context, project *types.Project, options api.KillOptions) error { - return api.ErrNotImplemented -} - -func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project, options api.ConvertOptions) ([]byte, error) { - err := b.resolveServiceImagesDigests(ctx, project) - if err != nil { - return nil, err - } - - template, err := b.convert(ctx, project) - if err != nil { - return nil, err - } - - bytes, err := marshall(template, options.Format) - if err != nil { - return nil, err - } - - x, ok := project.Extensions[extensionCloudFormation] - if !ok { - return bytes, nil - } - if options.Format != "yaml" { - return nil, fmt.Errorf("format %q with overlays is not supported", options.Format) - } - - nodes, err := yaml.Parse(string(bytes)) - if err != nil { - return nil, err - } - - bytes, err = yaml.Marshal(x) - if err != nil { - return nil, err - } - overlay, err := yaml.Parse(string(bytes)) - if err != nil { - return nil, err - } - nodes, err = merge2.Merge(overlay, nodes, yaml.MergeOptions{ - ListIncreaseDirection: yaml.MergeOptionsListPrepend, - }) - if err != nil { - return nil, err - } - - s, err := nodes.String() - if err != nil { - return nil, err - } - bytes = []byte(s) - return bytes, err -} - -func (b *ecsAPIService) resolveServiceImagesDigests(ctx context.Context, project *types.Project) error { - configFile, err := cliconfig.Load(config.Dir()) - if err != nil { - return err - } - - resolver := remotes.CreateResolver(configFile) - return project.ResolveImages(func(named reference.Named) (digest.Digest, error) { - _, desc, err := resolver.Resolve(ctx, named.String()) - return desc.Digest, err - }) -} - -func (b *ecsAPIService) convert(ctx context.Context, project *types.Project) (*cloudformation.Template, error) { - err := b.checkCompatibility(project) - if err != nil { - return nil, err - } - - template := cloudformation.NewTemplate() - resources, err := b.parse(ctx, project, template) - if err != nil { - return nil, err - } - - err = b.ensureResources(&resources, project, template) - if err != nil { - return nil, err - } - - for name, secret := range project.Secrets { - err := b.createSecret(project, name, secret, template) - if err != nil { - return nil, err - } - } - - b.createLogGroup(project, template) - - // Private DNS namespace will allow DNS name for the services to be ..local - b.createCloudMap(project, template, resources.vpc) - - b.createNFSMountTarget(project, resources, template) - - b.createAccessPoints(project, resources, template) - - for _, service := range project.Services { - err := b.createService(project, service, template, resources) - if err != nil { - return nil, err - } - - err = b.createAutoscalingPolicy(project, resources, template, service) - if err != nil { - return nil, err - } - } - - err = b.createCapacityProvider(ctx, project, template, resources) - if err != nil { - return nil, err - } - - return template, nil -} - -func (b *ecsAPIService) createService(project *types.Project, service types.ServiceConfig, template *cloudformation.Template, resources awsResources) error { - taskExecutionRole := b.createTaskExecutionRole(project, service, template) - taskRole := b.createTaskRole(project, service, template, resources) - - definition, err := b.createTaskDefinition(project, service, resources) - if err != nil { - return err - } - definition.ExecutionRoleArn = cloudformation.Ref(taskExecutionRole) - if taskRole != "" { - definition.TaskRoleArn = cloudformation.Ref(taskRole) - } - - taskDefinition := fmt.Sprintf("%sTaskDefinition", normalizeResourceName(service.Name)) - template.Resources[taskDefinition] = definition - - var healthCheck *cloudmap.Service_HealthCheckConfig - serviceRegistry := b.createServiceRegistry(service, template, healthCheck) - - var ( - dependsOn []string - serviceLB []ecs.Service_LoadBalancer - ) - for _, port := range service.Ports { - for net := range service.Networks { - b.createIngress(service, net, port, template, resources) - } - - protocol := strings.ToUpper(port.Protocol) - if resources.loadBalancerType == elbv2.LoadBalancerTypeEnumApplication { - // we don't set Https as a certificate must be specified for HTTPS listeners - protocol = elbv2.ProtocolEnumHttp - } - targetGroupName := b.createTargetGroup(project, service, port, template, protocol, resources.vpc) - listenerName := b.createListener(service, port, template, targetGroupName, resources.loadBalancer, protocol) - dependsOn = append(dependsOn, listenerName) - serviceLB = append(serviceLB, ecs.Service_LoadBalancer{ - ContainerName: service.Name, - ContainerPort: int(port.Target), - TargetGroupArn: cloudformation.Ref(targetGroupName), - }) - } - - desiredCount := 1 - if service.Deploy != nil && service.Deploy.Replicas != nil { - desiredCount = int(*service.Deploy.Replicas) - } - - for dependency := range service.DependsOn { - dependsOn = append(dependsOn, serviceResourceName(dependency)) - } - - for _, s := range service.Volumes { - dependsOn = append(dependsOn, b.mountTargets(s.Source, resources)...) - } - - minPercent, maxPercent, err := computeRollingUpdateLimits(service) - if err != nil { - return err - } - - assignPublicIP := ecsapi.AssignPublicIpEnabled - launchType := ecsapi.LaunchTypeFargate - platformVersion := "1.4.0" // LATEST which is set to 1.3.0 (?) which doesn’t allow efs volumes. - if requireEC2(service) { - assignPublicIP = ecsapi.AssignPublicIpDisabled - launchType = ecsapi.LaunchTypeEc2 - platformVersion = "" // The platform version must be null when specifying an EC2 launch type - } - - template.Resources[serviceResourceName(service.Name)] = &ecs.Service{ - AWSCloudFormationDependsOn: dependsOn, - Cluster: resources.cluster.ARN(), - DesiredCount: desiredCount, - DeploymentController: &ecs.Service_DeploymentController{ - Type: ecsapi.DeploymentControllerTypeEcs, - }, - DeploymentConfiguration: &ecs.Service_DeploymentConfiguration{ - MaximumPercent: maxPercent, - MinimumHealthyPercent: minPercent, - }, - LaunchType: launchType, - // TODO we miss support for https://github.com/aws/containers-roadmap/issues/631 to select a capacity provider - LoadBalancers: serviceLB, - NetworkConfiguration: &ecs.Service_NetworkConfiguration{ - AwsvpcConfiguration: &ecs.Service_AwsVpcConfiguration{ - AssignPublicIp: assignPublicIP, - SecurityGroups: resources.serviceSecurityGroups(service), - Subnets: resources.subnetsIDs(), - }, - }, - PlatformVersion: platformVersion, - PropagateTags: ecsapi.PropagateTagsService, - SchedulingStrategy: ecsapi.SchedulingStrategyReplica, - ServiceRegistries: []ecs.Service_ServiceRegistry{serviceRegistry}, - Tags: serviceTags(project, service), - TaskDefinition: cloudformation.Ref(normalizeResourceName(taskDefinition)), - } - return nil -} - -const allProtocols = "-1" - -func (b *ecsAPIService) createIngress(service types.ServiceConfig, net string, port types.ServicePortConfig, template *cloudformation.Template, resources awsResources) { - protocol := strings.ToUpper(port.Protocol) - if protocol == "" { - protocol = allProtocols - } - ingress := fmt.Sprintf("%s%dIngress", normalizeResourceName(net), port.Target) - template.Resources[ingress] = &ec2.SecurityGroupIngress{ - CidrIp: "0.0.0.0/0", - Description: fmt.Sprintf("%s:%d/%s on %s network", service.Name, port.Target, port.Protocol, net), - GroupId: resources.securityGroups[net], - FromPort: int(port.Target), - IpProtocol: protocol, - ToPort: int(port.Target), - } -} - -func (b *ecsAPIService) createSecret(project *types.Project, name string, s types.SecretConfig, template *cloudformation.Template) error { - if s.External.External { - return nil - } - sensitiveData, err := ioutil.ReadFile(s.File) - if err != nil { - return err - } - - resource := fmt.Sprintf("%sSecret", normalizeResourceName(s.Name)) - template.Resources[resource] = &secretsmanager.Secret{ - Description: fmt.Sprintf("Secret %s", s.Name), - SecretString: string(sensitiveData), - Tags: projectTags(project), - } - s.Name = cloudformation.Ref(resource) - project.Secrets[name] = s - return nil -} - -func (b *ecsAPIService) createLogGroup(project *types.Project, template *cloudformation.Template) { - retention := 0 - if v, ok := project.Extensions[extensionRetention]; ok { - retention = v.(int) - } - logGroup := fmt.Sprintf("/docker-compose/%s", project.Name) - template.Resources["LogGroup"] = &logs.LogGroup{ - LogGroupName: logGroup, - RetentionInDays: retention, - } -} - -func computeRollingUpdateLimits(service types.ServiceConfig) (int, int, error) { - maxPercent := 200 - minPercent := 100 - if service.Deploy == nil || service.Deploy.UpdateConfig == nil { - return minPercent, maxPercent, nil - } - updateConfig := service.Deploy.UpdateConfig - min, okMin := updateConfig.Extensions[extensionMinPercent] - if okMin { - minPercent = min.(int) - } - max, okMax := updateConfig.Extensions[extensionMaxPercent] - if okMax { - maxPercent = max.(int) - } - if okMin && okMax { - return minPercent, maxPercent, nil - } - - if updateConfig.Parallelism != nil { - parallelism := int(*updateConfig.Parallelism) - if service.Deploy.Replicas == nil { - return minPercent, maxPercent, - fmt.Errorf("rolling update configuration require deploy.replicas to be set") - } - replicas := int(*service.Deploy.Replicas) - if replicas < parallelism { - return minPercent, maxPercent, - fmt.Errorf("deploy.replicas (%d) must be greater than deploy.update_config.parallelism (%d)", replicas, parallelism) - } - if !okMin { - minPercent = (replicas - parallelism) * 100 / replicas - } - if !okMax { - maxPercent = (replicas + parallelism) * 100 / replicas - } - } - return minPercent, maxPercent, nil -} - -func (b *ecsAPIService) createListener(service types.ServiceConfig, port types.ServicePortConfig, - template *cloudformation.Template, - targetGroupName string, loadBalancer awsResource, protocol string) string { - listenerName := fmt.Sprintf( - "%s%s%dListener", - normalizeResourceName(service.Name), - strings.ToUpper(port.Protocol), - port.Target, - ) - //add listener to dependsOn - //https://stackoverflow.com/questions/53971873/the-target-group-does-not-have-an-associated-load-balancer - template.Resources[listenerName] = &elasticloadbalancingv2.Listener{ - DefaultActions: []elasticloadbalancingv2.Listener_Action{ - { - ForwardConfig: &elasticloadbalancingv2.Listener_ForwardConfig{ - TargetGroups: []elasticloadbalancingv2.Listener_TargetGroupTuple{ - { - TargetGroupArn: cloudformation.Ref(targetGroupName), - }, - }, - }, - Type: elbv2.ActionTypeEnumForward, - }, - }, - LoadBalancerArn: loadBalancer.ARN(), - Protocol: protocol, - Port: int(port.Target), - } - return listenerName -} - -func (b *ecsAPIService) createTargetGroup(project *types.Project, service types.ServiceConfig, port types.ServicePortConfig, template *cloudformation.Template, protocol string, vpc string) string { - targetGroupName := fmt.Sprintf( - "%s%s%dTargetGroup", - normalizeResourceName(service.Name), - strings.ToUpper(port.Protocol), - port.Published, - ) - template.Resources[targetGroupName] = &elasticloadbalancingv2.TargetGroup{ - Port: int(port.Target), - Protocol: protocol, - Tags: projectTags(project), - TargetType: elbv2.TargetTypeEnumIp, - VpcId: vpc, - } - return targetGroupName -} - -func (b *ecsAPIService) createServiceRegistry(service types.ServiceConfig, template *cloudformation.Template, healthCheck *cloudmap.Service_HealthCheckConfig) ecs.Service_ServiceRegistry { - serviceRegistration := fmt.Sprintf("%sServiceDiscoveryEntry", normalizeResourceName(service.Name)) - serviceRegistry := ecs.Service_ServiceRegistry{ - RegistryArn: cloudformation.GetAtt(serviceRegistration, "Arn"), - } - - template.Resources[serviceRegistration] = &cloudmap.Service{ - Description: fmt.Sprintf("%q service discovery entry in Cloud Map", service.Name), - HealthCheckConfig: healthCheck, - HealthCheckCustomConfig: &cloudmap.Service_HealthCheckCustomConfig{ - FailureThreshold: 1, - }, - Name: service.Name, - NamespaceId: cloudformation.Ref("CloudMap"), - DnsConfig: &cloudmap.Service_DnsConfig{ - DnsRecords: []cloudmap.Service_DnsRecord{ - { - TTL: 60, - Type: cloudmapapi.RecordTypeA, - }, - }, - RoutingPolicy: cloudmapapi.RoutingPolicyMultivalue, - }, - } - return serviceRegistry -} - -func (b *ecsAPIService) createTaskExecutionRole(project *types.Project, service types.ServiceConfig, template *cloudformation.Template) string { - taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", normalizeResourceName(service.Name)) - policies := b.createPolicies(project, service) - template.Resources[taskExecutionRole] = &iam.Role{ - AssumeRolePolicyDocument: ecsTaskAssumeRolePolicyDocument, - Policies: policies, - ManagedPolicyArns: []string{ - ecsTaskExecutionPolicy, - ecrReadOnlyPolicy, - }, - Tags: serviceTags(project, service), - } - return taskExecutionRole -} - -func (b *ecsAPIService) createTaskRole(project *types.Project, service types.ServiceConfig, template *cloudformation.Template, resources awsResources) string { - taskRole := fmt.Sprintf("%sTaskRole", normalizeResourceName(service.Name)) - rolePolicies := []iam.Role_Policy{} - if roles, ok := service.Extensions[extensionRole]; ok { - rolePolicies = append(rolePolicies, iam.Role_Policy{ - PolicyName: fmt.Sprintf("%sPolicy", normalizeResourceName(service.Name)), - PolicyDocument: roles, - }) - } - for _, vol := range service.Volumes { - rolePolicies = append(rolePolicies, iam.Role_Policy{ - PolicyName: fmt.Sprintf("%s%sVolumeMountPolicy", normalizeResourceName(service.Name), normalizeResourceName(vol.Source)), - PolicyDocument: volumeMountPolicyDocument(vol.Source, resources.filesystems[vol.Source].ARN()), - }) - } - managedPolicies := []string{} - if v, ok := service.Extensions[extensionManagedPolicies]; ok { - for _, s := range v.([]interface{}) { - managedPolicies = append(managedPolicies, s.(string)) - } - } - if len(rolePolicies) == 0 && len(managedPolicies) == 0 { - return "" - } - template.Resources[taskRole] = &iam.Role{ - AssumeRolePolicyDocument: ecsTaskAssumeRolePolicyDocument, - Policies: rolePolicies, - ManagedPolicyArns: managedPolicies, - Tags: serviceTags(project, service), - } - return taskRole -} - -func (b *ecsAPIService) createCloudMap(project *types.Project, template *cloudformation.Template, vpc string) { - template.Resources["CloudMap"] = &cloudmap.PrivateDnsNamespace{ - Description: fmt.Sprintf("Service Map for Docker Compose project %s", project.Name), - Name: fmt.Sprintf("%s.local", project.Name), - Vpc: vpc, - } -} - -func (b *ecsAPIService) createPolicies(project *types.Project, service types.ServiceConfig) []iam.Role_Policy { - var arns []string - if value, ok := service.Extensions[extensionPullCredentials]; ok { - arns = append(arns, value.(string)) - } - for _, secret := range service.Secrets { - arns = append(arns, project.Secrets[secret.Source].Name) - } - if len(arns) > 0 { - return []iam.Role_Policy{ - { - PolicyDocument: &PolicyDocument{ - Statement: []PolicyStatement{ - { - Effect: "Allow", - Action: []string{actionGetSecretValue, actionGetParameters, actionDecrypt}, - Resource: arns, - }, - }, - }, - PolicyName: fmt.Sprintf("%sGrantAccessToSecrets", service.Name), - }, - } - } - return nil -} - -func networkResourceName(network string) string { - return fmt.Sprintf("%sNetwork", normalizeResourceName(network)) -} - -func serviceResourceName(service string) string { - return fmt.Sprintf("%sService", normalizeResourceName(service)) -} - -func volumeResourceName(service string) string { - return fmt.Sprintf("%sFilesystem", normalizeResourceName(service)) -} - -func normalizeResourceName(s string) string { - return strings.Title(regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString(s, "")) -} diff --git a/ecs/cloudformation_test.go b/ecs/cloudformation_test.go deleted file mode 100644 index eb70773bb..000000000 --- a/ecs/cloudformation_test.go +++ /dev/null @@ -1,617 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - "fmt" - "io/ioutil" - "reflect" - "testing" - - "github.com/docker/compose-cli/pkg/api" - - "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/awslabs/goformation/v4/cloudformation" - "github.com/awslabs/goformation/v4/cloudformation/ec2" - "github.com/awslabs/goformation/v4/cloudformation/ecs" - "github.com/awslabs/goformation/v4/cloudformation/efs" - "github.com/awslabs/goformation/v4/cloudformation/elasticloadbalancingv2" - "github.com/awslabs/goformation/v4/cloudformation/iam" - "github.com/awslabs/goformation/v4/cloudformation/logs" - "github.com/compose-spec/compose-go/loader" - "github.com/compose-spec/compose-go/types" - "github.com/golang/mock/gomock" - "gotest.tools/v3/assert" - "gotest.tools/v3/golden" -) - -func TestSimpleConvert(t *testing.T) { - bytes, err := ioutil.ReadFile("testdata/input/simple-single-service.yaml") - assert.NilError(t, err) - template := convertYaml(t, string(bytes), useDefaultVPC) - resultAsJSON, err := marshall(template, "yaml") - assert.NilError(t, err) - result := fmt.Sprintf("%s\n", string(resultAsJSON)) - expected := "simple-cloudformation-conversion.golden" - golden.Assert(t, result, expected) -} - -func TestLogging(t *testing.T) { - template := convertYaml(t, ` -services: - foo: - image: hello_world - logging: - options: - awslogs-datetime-pattern: "FOO" - -x-aws-logs_retention: 10 -`, useDefaultVPC) - def := template.Resources["FooTaskDefinition"].(*ecs.TaskDefinition) - logging := getMainContainer(def, t).LogConfiguration - if logging != nil { - assert.Equal(t, logging.Options["awslogs-datetime-pattern"], "FOO") - } else { - t.Fatal("Logging not configured") - } - - logGroup := template.Resources["LogGroup"].(*logs.LogGroup) - assert.Equal(t, logGroup.RetentionInDays, 10) -} - -func TestEnvFile(t *testing.T) { - template := convertYaml(t, ` -services: - foo: - image: hello_world - env_file: - - testdata/input/envfile -`, useDefaultVPC) - def := template.Resources["FooTaskDefinition"].(*ecs.TaskDefinition) - env := getMainContainer(def, t).Environment - var found bool - for _, pair := range env { - if pair.Name == "FOO" { - assert.Equal(t, pair.Value, "BAR") - found = true - } - } - assert.Check(t, found, "environment variable FOO not set") -} - -func TestEnvFileAndEnv(t *testing.T) { - template := convertYaml(t, ` -services: - foo: - image: hello_world - env_file: - - testdata/input/envfile - environment: - - "FOO=ZOT" -`, useDefaultVPC) - def := template.Resources["FooTaskDefinition"].(*ecs.TaskDefinition) - env := getMainContainer(def, t).Environment - var found bool - for _, pair := range env { - if pair.Name == "FOO" { - assert.Equal(t, pair.Value, "ZOT") - found = true - } - } - assert.Check(t, found, "environment variable FOO not set") -} - -func TestRollingUpdateLimits(t *testing.T) { - template := convertYaml(t, ` -services: - foo: - image: hello_world - deploy: - replicas: 4 - update_config: - parallelism: 2 -`, useDefaultVPC) - service := template.Resources["FooService"].(*ecs.Service) - assert.Check(t, service.DeploymentConfiguration.MaximumPercent == 150) - assert.Check(t, service.DeploymentConfiguration.MinimumHealthyPercent == 50) -} - -func TestRollingUpdateExtension(t *testing.T) { - template := convertYaml(t, ` -services: - foo: - image: hello_world - deploy: - update_config: - x-aws-min_percent: 25 - x-aws-max_percent: 125 -`, useDefaultVPC) - service := template.Resources["FooService"].(*ecs.Service) - assert.Check(t, service.DeploymentConfiguration.MaximumPercent == 125) - assert.Check(t, service.DeploymentConfiguration.MinimumHealthyPercent == 25) -} - -func TestRolePolicy(t *testing.T) { - template := convertYaml(t, ` -services: - foo: - image: hello_world - x-aws-pull_credentials: "secret" -`, useDefaultVPC) - x := template.Resources["FooTaskExecutionRole"] - assert.Check(t, x != nil) - role := *(x.(*iam.Role)) - assert.Check(t, role.ManagedPolicyArns[0] == ecsTaskExecutionPolicy) - assert.Check(t, role.ManagedPolicyArns[1] == ecrReadOnlyPolicy) - // We expect an extra policy has been created for x-aws-pull_credentials - assert.Check(t, len(role.Policies) == 1) - policy := role.Policies[0].PolicyDocument.(*PolicyDocument) - expected := []string{"secretsmanager:GetSecretValue", "ssm:GetParameters", "kms:Decrypt"} - assert.DeepEqual(t, expected, policy.Statement[0].Action) - assert.DeepEqual(t, []string{"secret"}, policy.Statement[0].Resource) -} - -func TestMapNetworksToSecurityGroups(t *testing.T) { - template := convertYaml(t, ` -services: - test: - image: hello_world - networks: - - front-tier - - back-tier - -networks: - front-tier: - name: public - back-tier: - internal: true -`, useDefaultVPC) - assert.Check(t, template.Resources["FronttierNetwork"] != nil) - assert.Check(t, template.Resources["BacktierNetwork"] != nil) - assert.Check(t, template.Resources["BacktierNetworkIngress"] != nil) - i := template.Resources["FronttierNetworkIngress"] - assert.Check(t, i != nil) - ingress := *i.(*ec2.SecurityGroupIngress) - assert.Check(t, ingress.SourceSecurityGroupId == cloudformation.Ref("FronttierNetwork")) - -} - -func TestLoadBalancerTypeApplication(t *testing.T) { - cases := []string{ - `services: - test: - image: nginx - ports: - - 80:80 -`, - `services: - test: - image: nginx - ports: - - target: 8080 - x-aws-protocol: http -`, - } - for _, y := range cases { - template := convertYaml(t, y, useDefaultVPC) - lb := template.Resources["LoadBalancer"] - assert.Check(t, lb != nil) - loadBalancer := *lb.(*elasticloadbalancingv2.LoadBalancer) - assert.Check(t, len(loadBalancer.Name) <= 32) - assert.Check(t, loadBalancer.Type == elbv2.LoadBalancerTypeEnumApplication) - assert.Check(t, len(loadBalancer.SecurityGroups) > 0) - } -} - -func TestNoLoadBalancerIfNoPortExposed(t *testing.T) { - template := convertYaml(t, ` -services: - test: - image: nginx - foo: - image: bar -`, useDefaultVPC) - for _, r := range template.Resources { - assert.Check(t, r.AWSCloudFormationType() != "AWS::ElasticLoadBalancingV2::TargetGroup") - assert.Check(t, r.AWSCloudFormationType() != "AWS::ElasticLoadBalancingV2::Listener") - assert.Check(t, r.AWSCloudFormationType() != "AWS::ElasticLoadBalancingV2::PortPublisher") - } -} - -func TestServiceReplicas(t *testing.T) { - template := convertYaml(t, ` -services: - test: - image: nginx - deploy: - replicas: 10 -`, useDefaultVPC) - s := template.Resources["TestService"] - assert.Check(t, s != nil) - service := *s.(*ecs.Service) - assert.Check(t, service.DesiredCount == 10) -} - -func TestTaskSizeConvert(t *testing.T) { - template := convertYaml(t, ` -services: - test: - image: nginx -`, useDefaultVPC) - def := template.Resources["TestTaskDefinition"].(*ecs.TaskDefinition) - assert.Equal(t, def.Cpu, "256") - assert.Equal(t, def.Memory, "512") - - template = convertYaml(t, ` -services: - test: - image: nginx - deploy: - resources: - limits: - cpus: '0.5' - memory: 2048M -`, useDefaultVPC) - def = template.Resources["TestTaskDefinition"].(*ecs.TaskDefinition) - assert.Equal(t, def.Cpu, "512") - assert.Equal(t, def.Memory, "2048") - - template = convertYaml(t, ` -services: - test: - image: nginx - deploy: - resources: - limits: - cpus: '4' - memory: 8192M -`, useDefaultVPC) - def = template.Resources["TestTaskDefinition"].(*ecs.TaskDefinition) - assert.Equal(t, def.Cpu, "4096") - assert.Equal(t, def.Memory, "8192") - - template = convertYaml(t, ` -services: - test: - image: nginx - deploy: - resources: - limits: - cpus: '4' - memory: 792Mb - reservations: - generic_resources: - - discrete_resource_spec: - kind: gpus - value: 2 -`, useDefaultVPC, useGPU) - def = template.Resources["TestTaskDefinition"].(*ecs.TaskDefinition) - assert.Equal(t, def.Cpu, "4000") - assert.Equal(t, def.Memory, "792") - - template = convertYaml(t, ` -services: - test: - image: nginx - deploy: - resources: - reservations: - generic_resources: - - discrete_resource_spec: - kind: gpus - value: 2 -`, useDefaultVPC, useGPU) - def = template.Resources["TestTaskDefinition"].(*ecs.TaskDefinition) - assert.Equal(t, def.Cpu, "") - assert.Equal(t, def.Memory, "") - - template = convertYaml(t, ` -services: - test: - image: nginx - deploy: - resources: - reservations: - devices: - - capabilities: [gpu] - count: 2 -`, useDefaultVPC, useGPU) - def = template.Resources["TestTaskDefinition"].(*ecs.TaskDefinition) - assert.Equal(t, def.Cpu, "") - assert.Equal(t, def.Memory, "") -} - -func TestLoadBalancerTypeNetwork(t *testing.T) { - template := convertYaml(t, ` -services: - test: - image: nginx - ports: - - 80:80 - - 88:88 -`, useDefaultVPC) - lb := template.Resources["LoadBalancer"] - assert.Check(t, lb != nil) - loadBalancer := *lb.(*elasticloadbalancingv2.LoadBalancer) - assert.Check(t, loadBalancer.Type == elbv2.LoadBalancerTypeEnumNetwork) -} - -func TestUseExternalNetwork(t *testing.T) { - template := convertYaml(t, ` -services: - test: - image: nginx -networks: - default: - external: true - name: sg-123abc -`, useDefaultVPC, func(m *MockAPIMockRecorder) { - m.SecurityGroupExists(gomock.Any(), "sg-123abc").Return(true, nil) - }) - assert.Check(t, template.Resources["DefaultNetwork"] == nil) - assert.Check(t, template.Resources["DefaultNetworkIngress"] == nil) - s := template.Resources["TestService"].(*ecs.Service) - assert.Check(t, s != nil) //nolint:staticcheck - assert.Check(t, s.NetworkConfiguration.AwsvpcConfiguration.SecurityGroups[0] == "sg-123abc") //nolint:staticcheck -} - -func TestUseExternalVolume(t *testing.T) { - template := convertYaml(t, ` -services: - test: - image: nginx -volumes: - db-data: - external: true - name: fs-123abc -`, useDefaultVPC, func(m *MockAPIMockRecorder) { - m.ResolveFileSystem(gomock.Any(), "fs-123abc").Return(existingAWSResource{id: "fs-123abc"}, nil) - }) - s := template.Resources["DbdataNFSMountTargetOnSubnet1"].(*efs.MountTarget) - assert.Check(t, s != nil) //nolint:staticcheck - assert.Equal(t, s.FileSystemId, "fs-123abc") //nolint:staticcheck - - s = template.Resources["DbdataNFSMountTargetOnSubnet2"].(*efs.MountTarget) - assert.Check(t, s != nil) //nolint:staticcheck - assert.Equal(t, s.FileSystemId, "fs-123abc") //nolint:staticcheck -} - -func TestCreateVolume(t *testing.T) { - template := convertYaml(t, ` -services: - test: - image: nginx -volumes: - db-data: - driver_opts: - backup_policy: ENABLED - lifecycle_policy: AFTER_30_DAYS - performance_mode: maxIO - throughput_mode: provisioned - provisioned_throughput: 1024 -`, useDefaultVPC, func(m *MockAPIMockRecorder) { - m.ListFileSystems(gomock.Any(), map[string]string{ - api.ProjectLabel: t.Name(), - api.VolumeLabel: "db-data", - }).Return(nil, nil) - }) - n := volumeResourceName("db-data") - f := template.Resources[n].(*efs.FileSystem) - assert.Check(t, f != nil) //nolint:staticcheck - assert.Equal(t, f.BackupPolicy.Status, "ENABLED") //nolint:staticcheck - assert.Equal(t, f.LifecyclePolicies[0].TransitionToIA, "AFTER_30_DAYS") //nolint:staticcheck - assert.Equal(t, f.PerformanceMode, "maxIO") //nolint:staticcheck - assert.Equal(t, f.ThroughputMode, "provisioned") //nolint:staticcheck - assert.Equal(t, f.ProvisionedThroughputInMibps, float64(1024)) //nolint:staticcheck - - s := template.Resources["DbdataNFSMountTargetOnSubnet1"].(*efs.MountTarget) - assert.Check(t, s != nil) //nolint:staticcheck - assert.Equal(t, s.FileSystemId, cloudformation.Ref(n)) //nolint:staticcheck -} - -func TestCreateAccessPoint(t *testing.T) { - template := convertYaml(t, ` -services: - test: - image: nginx -volumes: - db-data: - driver_opts: - uid: 1002 - gid: 1002 -`, useDefaultVPC, func(m *MockAPIMockRecorder) { - m.ListFileSystems(gomock.Any(), gomock.Any()).Return(nil, nil) - }) - a := template.Resources["DbdataAccessPoint"].(*efs.AccessPoint) - assert.Check(t, a != nil) //nolint:staticcheck - assert.Equal(t, a.PosixUser.Uid, "1002") //nolint:staticcheck - assert.Equal(t, a.PosixUser.Gid, "1002") //nolint:staticcheck -} - -func TestReusePreviousVolume(t *testing.T) { - template := convertYaml(t, ` -services: - test: - image: nginx -volumes: - db-data: {} -`, useDefaultVPC, func(m *MockAPIMockRecorder) { - m.ListFileSystems(gomock.Any(), map[string]string{ - api.ProjectLabel: t.Name(), - api.VolumeLabel: "db-data", - }).Return([]awsResource{ - existingAWSResource{ - id: "fs-123abc", - }, - }, nil) - }) - s := template.Resources["DbdataNFSMountTargetOnSubnet1"].(*efs.MountTarget) - assert.Check(t, s != nil) //nolint:staticcheck - assert.Equal(t, s.FileSystemId, "fs-123abc") //nolint:staticcheck -} - -func TestServiceMapping(t *testing.T) { - template := convertYaml(t, ` -services: - test: - image: "image" - command: "command" - entrypoint: "entrypoint" - environment: - - "FOO=BAR" - cap_add: - - SYS_PTRACE - cap_drop: - - SYSLOG - init: true - user: "user" - working_dir: "working_dir" -`, useDefaultVPC) - def := template.Resources["TestTaskDefinition"].(*ecs.TaskDefinition) - container := getMainContainer(def, t) - assert.Equal(t, container.Image, "image") - assert.Equal(t, container.Command[0], "command") - assert.Equal(t, container.EntryPoint[0], "entrypoint") - assert.Equal(t, get(container.Environment, "FOO"), "BAR") - assert.Check(t, container.LinuxParameters.InitProcessEnabled) - assert.Equal(t, container.LinuxParameters.Capabilities.Add[0], "SYS_PTRACE") - assert.Equal(t, container.LinuxParameters.Capabilities.Drop[0], "SYSLOG") - assert.Equal(t, container.User, "user") - assert.Equal(t, container.WorkingDirectory, "working_dir") -} - -func get(l []ecs.TaskDefinition_KeyValuePair, name string) string { - for _, e := range l { - if e.Name == name { - return e.Value - } - } - return "" -} - -func TestResourcesHaveProjectTagSet(t *testing.T) { - template := convertYaml(t, ` -services: - test: - image: nginx - ports: - - 80:80 - - 88:88 -`, useDefaultVPC) - for _, r := range template.Resources { - tags := reflect.Indirect(reflect.ValueOf(r)).FieldByName("Tags") - if !tags.IsValid() { - continue - } - for i := 0; i < tags.Len(); i++ { - k := tags.Index(i).FieldByName("Key").String() - v := tags.Index(i).FieldByName("Value").String() - if k == api.ProjectLabel { - assert.Equal(t, v, t.Name()) - } - } - } -} - -func TestTemplateMetadata(t *testing.T) { - template := convertYaml(t, ` -x-aws-cluster: "arn:aws:ecs:region:account:cluster/name" -services: - test: - image: nginx -`, useDefaultVPC, func(m *MockAPIMockRecorder) { - m.ResolveCluster(gomock.Any(), "arn:aws:ecs:region:account:cluster/name").Return(existingAWSResource{ - arn: "arn:aws:ecs:region:account:cluster/name", - id: "name", - }, nil) - }) - assert.Equal(t, template.Metadata["Cluster"], "arn:aws:ecs:region:account:cluster/name") -} - -func TestARNUsedAsVpcID(t *testing.T) { - convertYaml(t, ` -x-aws-vpc: "arn:aws:ec2:us-west-1:EXAMPLE:vpc/vpc-1234acbd" -services: - test: - image: nginx -`, func(m *MockAPIMockRecorder) { - m.CheckVPC(gomock.Any(), "vpc-1234acbd").Return(nil) - m.GetSubNets(gomock.Any(), "vpc-1234acbd").Return([]awsResource{ - existingAWSResource{id: "subnet1"}, - existingAWSResource{id: "subnet2"}, - }, nil) - m.IsPublicSubnet(gomock.Any(), "subnet1").Return(true, nil) - m.IsPublicSubnet(gomock.Any(), "subnet2").Return(true, nil) - }) -} - -func convertYaml(t *testing.T, yaml string, fn ...func(m *MockAPIMockRecorder)) *cloudformation.Template { - project := loadConfig(t, yaml) - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - m := NewMockAPI(ctrl) - for _, f := range fn { - f(m.EXPECT()) - } - - backend := &ecsAPIService{ - aws: m, - } - template, err := backend.convert(context.TODO(), project) - assert.NilError(t, err) - return template -} - -func loadConfig(t *testing.T, yaml string) *types.Project { - dict, err := loader.ParseYAML([]byte(yaml)) - assert.NilError(t, err) - model, err := loader.Load(types.ConfigDetails{ - ConfigFiles: []types.ConfigFile{ - {Config: dict}, - }, - }, func(options *loader.Options) { - options.Name = t.Name() - }) - assert.NilError(t, err) - return model -} - -func getMainContainer(def *ecs.TaskDefinition, t *testing.T) ecs.TaskDefinition_ContainerDefinition { - for _, c := range def.ContainerDefinitions { - if c.Essential { - return c - } - } - t.Fail() - return def.ContainerDefinitions[0] -} - -func useDefaultVPC(m *MockAPIMockRecorder) { - m.GetDefaultVPC(gomock.Any()).Return("vpc-123", nil) - m.GetSubNets(gomock.Any(), "vpc-123").Return([]awsResource{ - existingAWSResource{id: "subnet1"}, - existingAWSResource{id: "subnet2"}, - }, nil) - m.IsPublicSubnet(gomock.Any(), "subnet1").Return(true, nil) - m.IsPublicSubnet(gomock.Any(), "subnet2").Return(true, nil) -} - -func useGPU(m *MockAPIMockRecorder) { - m.GetParameter(gomock.Any(), gomock.Any()).Return("", nil) -} diff --git a/ecs/compatibility.go b/ecs/compatibility.go deleted file mode 100644 index 814c239bd..000000000 --- a/ecs/compatibility.go +++ /dev/null @@ -1,177 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "fmt" - - "github.com/compose-spec/compose-go/compatibility" - "github.com/compose-spec/compose-go/errdefs" - "github.com/compose-spec/compose-go/types" - "github.com/sirupsen/logrus" -) - -func (b *ecsAPIService) checkCompatibility(project *types.Project) error { - var checker compatibility.Checker = &fargateCompatibilityChecker{ - AllowList: compatibility.AllowList{ - Supported: compatibleComposeAttributes, - }, - projet: project, - } - compatibility.Check(project, checker) - for _, err := range checker.Errors() { - if errdefs.IsIncompatibleError(err) { - return err - } - logrus.Warn(err.Error()) - } - if !compatibility.IsCompatible(checker) { - return fmt.Errorf("compose file is incompatible with Amazon ECS") - } - return nil -} - -type fargateCompatibilityChecker struct { - compatibility.AllowList - projet *types.Project -} - -var compatibleComposeAttributes = []string{ - "services.command", - "services.container_name", - "services.cap_drop", - "services.depends_on", - "services.deploy", - "services.deploy.placement", - "services.deploy.placement.constraints", - "services.deploy.replicas", - "services.deploy.resources.limits", - "services.deploy.resources.limits.cpus", - "services.deploy.resources.limits.memory", - "services.deploy.resources.reservations", - "services.deploy.resources.reservations.cpus", - "services.deploy.resources.reservations.memory", - "services.deploy.resources.reservations.devices", - "services.deploy.resources.reservations.devices.capabilities", - "services.deploy.resources.reservations.devices.count", - "services.deploy.resources.reservations.devices.driver", - "services.deploy.resources.reservations.generic_resources", - "services.deploy.resources.reservations.generic_resources.discrete_resource_spec", - "services.deploy.update_config", - "services.deploy.update_config.parallelism", - "services.entrypoint", - "services.environment", - "services.env_file", - "services.healthcheck", - "services.healthcheck.interval", - "services.healthcheck.retries", - "services.healthcheck.start_period", - "services.healthcheck.test", - "services.healthcheck.timeout", - "services.image", - "services.init", - "services.logging", - "services.logging.options", - "services.networks", - "services.ports", - "services.ports.mode", - "services.ports.target", - "services.ports.protocol", - "services.secrets", - "services.secrets.source", - "services.secrets.target", - "services.user", - "services.volumes", - "services.volumes.read_only", - "services.volumes.target", - "services.working_dir", - "secrets.external", - "secrets.name", - "secrets.file", - "volumes", - "volumes.external", - "volumes.name", - "volumes.driver_opts", - "networks.external", - "networks.name", -} - -func (c *fargateCompatibilityChecker) CheckImage(service *types.ServiceConfig) { - if service.Image == "" { - c.Incompatible("service %s doesn't define a Docker image to run", service.Name) - } -} - -func (c *fargateCompatibilityChecker) CheckPortsPublished(p *types.ServicePortConfig) { - if p.Published == 0 { - p.Published = p.Target - } - if p.Published != p.Target { - c.Incompatible("published port can't be set to a distinct value than container port") - } -} - -func (c *fargateCompatibilityChecker) CheckVolumesSource(config *types.ServiceVolumeConfig) { - if config.Type == types.VolumeTypeBind { - c.Incompatible("ECS Fargate does not support bind mounts from host") - } - if config.Type == types.VolumeTypeTmpfs { - c.Incompatible("ECS Fargate does not support tmpfs") - } -} - -func (c *fargateCompatibilityChecker) CheckCapAdd(service *types.ServiceConfig) { - add := []string{} - for _, cap := range service.CapAdd { - switch cap { - case "SYS_PTRACE": - add = append(add, cap) - default: - c.Incompatible("ECS doesn't allow to add capability %s", cap) - } - } - service.CapAdd = add -} - -func (c *fargateCompatibilityChecker) CheckLoggingDriver(config *types.LoggingConfig) { - if config.Driver != "" && config.Driver != "awslogs" { - c.Unsupported("services.logging.driver %s is not supported", config.Driver) - } -} - -func (c *fargateCompatibilityChecker) CheckUlimits(service *types.ServiceConfig) { - for k := range service.Ulimits { - if k != "nofile" { - c.Unsupported("services.ulimits.%s is not supported by Fargate", k) - delete(service.Ulimits, k) - } - } -} - -func (c *fargateCompatibilityChecker) CheckDeployResourcesDevicesCapabilities(s string, r types.DeviceRequest) { - for _, cap := range r.Capabilities { - if cap != "gpu" { - c.Unsupported("services.deploy.resources.%s.devices.capabilities = %s", s, cap) - } - } -} - -func (c *fargateCompatibilityChecker) CheckDeployResourcesDevicesDriver(s string, r types.DeviceRequest) { - if r.Driver != "" && r.Driver != "nvidia" { - c.Unsupported("services.deploy.resources.%s.devices.driver = %s", s, r.Driver) - } -} diff --git a/ecs/context.go b/ecs/context.go deleted file mode 100644 index 4566b5918..000000000 --- a/ecs/context.go +++ /dev/null @@ -1,432 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - "fmt" - "os" - "path/filepath" - "sort" - "strings" - - "github.com/docker/compose-cli/pkg/prompt" - - "github.com/AlecAivazis/survey/v2/terminal" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/defaults" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/ec2" - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/pkg/api" - "github.com/pkg/errors" - "gopkg.in/ini.v1" -) - -func getEnvVars() ContextParams { - c := ContextParams{ - Profile: os.Getenv("AWS_PROFILE"), - Region: os.Getenv("AWS_REGION"), - } - if c.Region == "" { - defaultRegion := os.Getenv("AWS_DEFAULT_REGION") - if defaultRegion == "" { - defaultRegion = "us-east-1" - } - c.Region = defaultRegion - } - - p := credentials.EnvProvider{} - creds, err := p.Retrieve() - if err != nil { - return c - } - c.AccessKey = creds.AccessKeyID - c.SecretKey = creds.SecretAccessKey - return c -} - -type contextCreateAWSHelper struct { - user prompt.UI - availableRegions func(opts *ContextParams) ([]string, error) -} - -func newContextCreateHelper() contextCreateAWSHelper { - return contextCreateAWSHelper{ - user: prompt.User{}, - availableRegions: listAvailableRegions, - } -} - -func (h contextCreateAWSHelper) createContextData(_ context.Context, opts ContextParams) (interface{}, string, error) { - if opts.CredsFromEnv { - // Explicit creation from ENV variables - ecsCtx, descr := h.createContext(&opts) - return ecsCtx, descr, nil - } else if opts.AccessKey != "" && opts.SecretKey != "" { - // Explicit creation using keys - err := h.createProfileFromCredentials(&opts) - if err != nil { - return nil, "", err - } - } else if opts.Profile != "" { - // Excplicit creation by selecting a profile - // check profile exists - profilesList, err := getProfiles() - if err != nil { - return nil, "", err - } - if !contains(profilesList, opts.Profile) { - return nil, "", errors.Wrapf(api.ErrNotFound, "profile %q not found", opts.Profile) - } - } else { - // interactive - var options []string - var actions []func(params *ContextParams) error - - if _, err := os.Stat(getAWSConfigFile()); err == nil { - // User has .aws/config file, so we can offer to select one of his profiles - options = append(options, "An existing AWS profile") - actions = append(actions, h.selectFromLocalProfile) - } - - options = append(options, "AWS secret and token credentials") - actions = append(actions, h.createProfileFromCredentials) - - options = append(options, "AWS environment variables") - actions = append(actions, func(params *ContextParams) error { - opts.CredsFromEnv = true - return nil - }) - - selected, err := h.user.Select("Create a Docker context using:", options) - if err != nil { - if err == terminal.InterruptErr { - return nil, "", api.ErrCanceled - } - return nil, "", err - } - - err = actions[selected](&opts) - if err != nil { - return nil, "", err - } - } - - ecsCtx, descr := h.createContext(&opts) - return ecsCtx, descr, nil -} - -func (h contextCreateAWSHelper) createContext(c *ContextParams) (interface{}, string) { - var description string - - if c.CredsFromEnv { - if c.Description == "" { - description = "credentials read from environment" - } - return store.EcsContext{ - CredentialsFromEnv: c.CredsFromEnv, - Profile: c.Profile, - }, description - } - - if c.Region != "" { - description = strings.TrimSpace( - fmt.Sprintf("%s (%s)", c.Description, c.Region)) - } - return store.EcsContext{ - Profile: c.Profile, - }, description -} - -func (h contextCreateAWSHelper) selectFromLocalProfile(opts *ContextParams) error { - profilesList, err := getProfiles() - if err != nil { - return err - } - opts.Profile, err = h.chooseProfile(profilesList) - return err -} - -func (h contextCreateAWSHelper) createProfileFromCredentials(opts *ContextParams) error { - if opts.AccessKey == "" || opts.SecretKey == "" { - fmt.Println("Retrieve or create AWS Access Key and Secret on https://console.aws.amazon.com/iam/home?#security_credential") - accessKey, secretKey, err := h.askCredentials() - if err != nil { - return err - } - opts.AccessKey = accessKey - opts.SecretKey = secretKey - } - - if opts.Region == "" { - err := h.chooseRegion(opts) - if err != nil { - return err - } - } - // save as a profile - if opts.Profile == "" { - opts.Profile = "default" - } - // context name used as profile name - err := h.saveCredentials(opts.Profile, opts.AccessKey, opts.SecretKey) - if err != nil { - return err - } - return h.saveRegion(opts.Profile, opts.Region) -} - -func (h contextCreateAWSHelper) saveCredentials(profile string, accessKeyID string, secretAccessKey string) error { - file := getAWSCredentialsFile() - err := os.MkdirAll(filepath.Dir(file), 0700) - if err != nil { - return err - } - - credentials, err := ini.Load(file) - if err != nil { - if !os.IsNotExist(err) { - return err - } - credentials = ini.Empty() - } - - section, err := credentials.NewSection(profile) - if err != nil { - return err - } - _, err = section.NewKey("aws_access_key_id", accessKeyID) - if err != nil { - return err - } - _, err = section.NewKey("aws_secret_access_key", secretAccessKey) - if err != nil { - return err - } - return credentials.SaveTo(file) -} - -func (h contextCreateAWSHelper) saveRegion(profile, region string) error { - if region == "" { - return nil - } - // loads ~/.aws/config - awsConfig := getAWSConfigFile() - configIni, err := ini.Load(awsConfig) - if err != nil { - if !os.IsNotExist(err) { - return err - } - configIni = ini.Empty() - } - profile = fmt.Sprintf("profile %s", profile) - section, err := configIni.GetSection(profile) - if err != nil { - if !strings.Contains(err.Error(), "does not exist") { - return err - } - section, err = configIni.NewSection(profile) - if err != nil { - return err - } - } - // save region under profile section in ~/.aws/config - _, err = section.NewKey("region", region) - if err != nil { - return err - } - return configIni.SaveTo(awsConfig) -} - -func getProfiles() ([]string, error) { - profiles := []string{} - // parse both .aws/credentials and .aws/config for profiles - configFiles := map[string]bool{ - getAWSCredentialsFile(): false, - getAWSConfigFile(): true, - } - for f, prefix := range configFiles { - sections, err := loadIniFile(f, prefix) - if err != nil { - if os.IsNotExist(err) { - continue - } - return nil, err - } - for key := range sections { - name := strings.ToLower(key) - if !contains(profiles, name) { - profiles = append(profiles, name) - } - } - } - sort.Slice(profiles, func(i, j int) bool { - return profiles[i] < profiles[j] - }) - - return profiles, nil -} - -func (h contextCreateAWSHelper) chooseProfile(profiles []string) (string, error) { - options := []string{} - options = append(options, profiles...) - - selected, err := h.user.Select("Select AWS Profile", options) - if err != nil { - if err == terminal.InterruptErr { - return "", api.ErrCanceled - } - return "", err - } - profile := options[selected] - return profile, nil -} - -func getRegion(profile string) (string, error) { - if profile == "" { - profile = "default" - } - // only load ~/.aws/config - awsConfig := defaults.SharedConfigFilename() - configIni, err := ini.Load(awsConfig) - if err != nil { - if !os.IsNotExist(err) { - return "", err - } - configIni = ini.Empty() - } - - getProfileRegion := func(p string) string { - r := "" - section, err := configIni.GetSection(p) - if err == nil { - reg, err := section.GetKey("region") - if err == nil { - r = reg.Value() - } - } - return r - } - if profile != "default" { - profile = fmt.Sprintf("profile %s", profile) - } - region := getProfileRegion(profile) - if region == "" { - region = getProfileRegion("default") - } - if region == "" { - // fallback to AWS default - region = "us-east-1" - } - return region, nil -} - -func (h contextCreateAWSHelper) chooseRegion(opts *ContextParams) error { - regions, err := h.availableRegions(opts) - if err != nil { - return err - } - // promp user for region - selected, err := h.user.Select("Region", regions) - if err != nil { - return err - } - opts.Region = regions[selected] - return nil -} - -func listAvailableRegions(opts *ContextParams) ([]string, error) { - // Setup SDK with credentials, will also validate those - session, err := session.NewSessionWithOptions(session.Options{ - Config: aws.Config{ - Credentials: credentials.NewStaticCredentials(opts.AccessKey, opts.SecretKey, ""), - Region: aws.String("us-east-1"), - }, - }) - if err != nil { - return nil, err - } - - desc, err := ec2.New(session).DescribeRegions(&ec2.DescribeRegionsInput{}) - if err != nil { - return nil, err - } - var regions []string - for _, r := range desc.Regions { - regions = append(regions, aws.StringValue(r.RegionName)) - } - return regions, nil -} - -func (h contextCreateAWSHelper) askCredentials() (string, string, error) { - accessKeyID, err := h.user.Input("AWS Access Key ID", "") - if err != nil { - return "", "", err - } - secretAccessKey, err := h.user.Password("Enter AWS Secret Access Key") - if err != nil { - return "", "", err - } - // validate access ID and password - if len(accessKeyID) < 3 || len(secretAccessKey) < 3 { - return "", "", fmt.Errorf("AWS Access/Secret Access Key must have more than 3 characters") - } - return accessKeyID, secretAccessKey, nil -} - -func contains(values []string, value string) bool { - for _, v := range values { - if v == value { - return true - } - } - return false -} - -func loadIniFile(path string, prefix bool) (map[string]ini.Section, error) { - profiles := map[string]ini.Section{} - credIni, err := ini.Load(path) - if err != nil { - return nil, err - } - for _, section := range credIni.Sections() { - if prefix && strings.HasPrefix(section.Name(), "profile ") { - profiles[section.Name()[len("profile "):]] = *section - } else if !prefix || section.Name() == "default" { - profiles[section.Name()] = *section - } - } - return profiles, nil -} - -func getAWSConfigFile() string { - awsConfig, ok := os.LookupEnv("AWS_CONFIG_FILE") - if !ok { - awsConfig = defaults.SharedConfigFilename() - } - return awsConfig -} - -func getAWSCredentialsFile() string { - awsConfig, ok := os.LookupEnv("AWS_SHARED_CREDENTIALS_FILE") - if !ok { - awsConfig = defaults.SharedCredentialsFilename() - } - return awsConfig -} diff --git a/ecs/context_test.go b/ecs/context_test.go deleted file mode 100644 index 1889e70fc..000000000 --- a/ecs/context_test.go +++ /dev/null @@ -1,173 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - "os" - "testing" - - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/pkg/prompt" - - "github.com/golang/mock/gomock" - "gotest.tools/v3/assert" - "gotest.tools/v3/fs" - "gotest.tools/v3/golden" -) - -func TestCreateContextDataFromEnv(t *testing.T) { - c := contextCreateAWSHelper{ - user: nil, - } - data, desc, err := c.createContextData(context.TODO(), ContextParams{ - Name: "test", - CredsFromEnv: true, - }) - assert.NilError(t, err) - assert.Equal(t, data.(store.EcsContext).CredentialsFromEnv, true) - assert.Equal(t, desc, "credentials read from environment") -} - -func TestCreateContextDataByKeys(t *testing.T) { - dir := fs.NewDir(t, "aws") - os.Setenv("AWS_CONFIG_FILE", dir.Join("config")) // nolint:errcheck - os.Setenv("AWS_SHARED_CREDENTIALS_FILE", dir.Join("credentials")) // nolint:errcheck - - defer os.Unsetenv("AWS_CONFIG_FILE") // nolint:errcheck - defer os.Unsetenv("AWS_SHARED_CREDENTIALS_FILE") // nolint:errcheck - - c := contextCreateAWSHelper{ - user: nil, - } - - data, _, err := c.createContextData(context.TODO(), ContextParams{ - Name: "test", - AccessKey: "ABCD", - SecretKey: "X&123", - Region: "eu-west-3", - }) - assert.NilError(t, err) - assert.Equal(t, data.(store.EcsContext).Profile, "default") - - s := golden.Get(t, dir.Join("config")) - golden.Assert(t, string(s), "context-by-keys-config.golden") - - s = golden.Get(t, dir.Join("credentials")) - golden.Assert(t, string(s), "context-by-keys-credentials.golden") -} - -func TestCreateContextDataFromProfile(t *testing.T) { - os.Setenv("AWS_CONFIG_FILE", "testdata/context-by-profile-config.golden") // nolint:errcheck - os.Setenv("AWS_SHARED_CREDENTIALS_FILE", "testdata/context-by-profile-credentials.golden") // nolint:errcheck - - defer os.Unsetenv("AWS_CONFIG_FILE") // nolint:errcheck - defer os.Unsetenv("AWS_SHARED_CREDENTIALS_FILE") // nolint:errcheck - - c := contextCreateAWSHelper{ - user: nil, - } - - data, _, err := c.createContextData(context.TODO(), ContextParams{ - Name: "test", - Profile: "foo", - }) - assert.NilError(t, err) - assert.Equal(t, data.(store.EcsContext).Profile, "foo") -} - -func TestCreateContextDataFromEnvInteractive(t *testing.T) { - dir := fs.NewDir(t, "aws") - os.Setenv("AWS_CONFIG_FILE", dir.Join("config")) // nolint:errcheck - os.Setenv("AWS_SHARED_CREDENTIALS_FILE", dir.Join("credentials")) // nolint:errcheck - - defer os.Unsetenv("AWS_CONFIG_FILE") // nolint:errcheck - defer os.Unsetenv("AWS_SHARED_CREDENTIALS_FILE") // nolint:errcheck - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - ui := prompt.NewMockUI(ctrl) - c := contextCreateAWSHelper{ - user: ui, - } - - ui.EXPECT().Select("Create a Docker context using:", gomock.Any()).Return(1, nil) - data, _, err := c.createContextData(context.TODO(), ContextParams{}) - assert.NilError(t, err) - assert.Equal(t, data.(store.EcsContext).CredentialsFromEnv, true) -} - -func TestCreateContextDataByKeysInteractive(t *testing.T) { - dir := fs.NewDir(t, "aws") - os.Setenv("AWS_CONFIG_FILE", dir.Join("config")) // nolint:errcheck - os.Setenv("AWS_SHARED_CREDENTIALS_FILE", dir.Join("credentials")) // nolint:errcheck - - defer os.Unsetenv("AWS_CONFIG_FILE") // nolint:errcheck - defer os.Unsetenv("AWS_SHARED_CREDENTIALS_FILE") // nolint:errcheck - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - ui := prompt.NewMockUI(ctrl) - c := contextCreateAWSHelper{ - user: ui, - availableRegions: func(opts *ContextParams) ([]string, error) { - return []string{"us-east-1", "eu-west-3"}, nil - }, - } - - ui.EXPECT().Select("Create a Docker context using:", gomock.Any()).Return(0, nil) - ui.EXPECT().Input("AWS Access Key ID", gomock.Any()).Return("ABCD", nil) - ui.EXPECT().Password("Enter AWS Secret Access Key").Return("X&123", nil) - ui.EXPECT().Select("Region", []string{"us-east-1", "eu-west-3"}).Return(1, nil) - - data, _, err := c.createContextData(context.TODO(), ContextParams{}) - assert.NilError(t, err) - assert.Equal(t, data.(store.EcsContext).Profile, "default") - - assert.NilError(t, err) - assert.Equal(t, data.(store.EcsContext).Profile, "default") - - s := golden.Get(t, dir.Join("config")) - golden.Assert(t, string(s), "context-by-keys-config.golden") - - s = golden.Get(t, dir.Join("credentials")) - golden.Assert(t, string(s), "context-by-keys-credentials.golden") -} - -func TestCreateContextDataByProfileInteractive(t *testing.T) { - os.Setenv("AWS_CONFIG_FILE", "testdata/context-by-profile-config.golden") // nolint:errcheck - os.Setenv("AWS_SHARED_CREDENTIALS_FILE", "testdata/context-by-profile-credentials.golden") // nolint:errcheck - - defer os.Unsetenv("AWS_CONFIG_FILE") // nolint:errcheck - defer os.Unsetenv("AWS_SHARED_CREDENTIALS_FILE") // nolint:errcheck - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - ui := prompt.NewMockUI(ctrl) - c := contextCreateAWSHelper{ - user: ui, - } - ui.EXPECT().Select("Create a Docker context using:", gomock.Any()).Return(0, nil) - ui.EXPECT().Select("Select AWS Profile", []string{"default", "foo"}).Return(1, nil) - - data, _, err := c.createContextData(context.TODO(), ContextParams{}) - assert.NilError(t, err) - assert.Equal(t, data.(store.EcsContext).Profile, "foo") -} diff --git a/ecs/convert.go b/ecs/convert.go deleted file mode 100644 index fc23a9618..000000000 --- a/ecs/convert.go +++ /dev/null @@ -1,592 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "sort" - "strconv" - "strings" - "time" - - "github.com/docker/compose-cli/ecs/secrets" - - ecsapi "github.com/aws/aws-sdk-go/service/ecs" - "github.com/awslabs/goformation/v4/cloudformation" - "github.com/awslabs/goformation/v4/cloudformation/ecs" - "github.com/compose-spec/compose-go/types" - "github.com/docker/cli/opts" - "github.com/joho/godotenv" -) - -const secretsInitContainerImage = "docker/ecs-secrets-sidecar:1.0" -const searchDomainInitContainerImage = "docker/ecs-searchdomain-sidecar:1.0" - -func (b *ecsAPIService) createTaskDefinition(project *types.Project, service types.ServiceConfig, resources awsResources) (*ecs.TaskDefinition, error) { - cpu, mem, err := toLimits(service) - if err != nil { - return nil, err - } - _, memReservation := toContainerReservation(service) - credential := getRepoCredentials(service) - - logConfiguration := getLogConfiguration(service, project) - - var ( - initContainers []ecs.TaskDefinition_ContainerDefinition - volumes []ecs.TaskDefinition_Volume - mounts []ecs.TaskDefinition_MountPoint - ) - if len(service.Secrets) > 0 { - secretsVolume, secretsMount, secretsSideCar, err := createSecretsSideCar(project, service, logConfiguration) - if err != nil { - return nil, err - } - initContainers = append(initContainers, secretsSideCar) - volumes = append(volumes, secretsVolume) - mounts = append(mounts, secretsMount) - } - - initContainers = append(initContainers, ecs.TaskDefinition_ContainerDefinition{ - Name: fmt.Sprintf("%s_ResolvConf_InitContainer", normalizeResourceName(service.Name)), - Image: searchDomainInitContainerImage, - Essential: false, - Command: []string{b.Region + ".compute.internal", project.Name + ".local"}, - LogConfiguration: logConfiguration, - }) - - var dependencies []ecs.TaskDefinition_ContainerDependency - for _, c := range initContainers { - dependencies = append(dependencies, ecs.TaskDefinition_ContainerDependency{ - Condition: ecsapi.ContainerConditionSuccess, - ContainerName: c.Name, - }) - } - - for _, v := range service.Volumes { - n := fmt.Sprintf("%sAccessPoint", normalizeResourceName(v.Source)) - volumes = append(volumes, ecs.TaskDefinition_Volume{ - EFSVolumeConfiguration: &ecs.TaskDefinition_EFSVolumeConfiguration{ - AuthorizationConfig: &ecs.TaskDefinition_AuthorizationConfig{ - AccessPointId: cloudformation.Ref(n), - IAM: "ENABLED", - }, - FilesystemId: resources.filesystems[v.Source].ID(), - TransitEncryption: "ENABLED", - }, - Name: v.Source, - }) - mounts = append(mounts, ecs.TaskDefinition_MountPoint{ - ContainerPath: v.Target, - ReadOnly: v.ReadOnly, - SourceVolume: v.Source, - }) - } - - pairs, err := createEnvironment(project, service) - if err != nil { - return nil, err - } - var reservations *types.Resource - if service.Deploy != nil && service.Deploy.Resources.Reservations != nil { - reservations = service.Deploy.Resources.Reservations - } - - containers := append(initContainers, ecs.TaskDefinition_ContainerDefinition{ - Command: service.Command, - DisableNetworking: service.NetworkMode == "none", - DependsOnProp: dependencies, - DnsSearchDomains: service.DNSSearch, - DnsServers: service.DNS, - DockerSecurityOptions: service.SecurityOpt, - EntryPoint: service.Entrypoint, - Environment: pairs, - Essential: true, - ExtraHosts: toHostEntryPtr(service.ExtraHosts), - FirelensConfiguration: nil, - HealthCheck: toHealthCheck(service.HealthCheck), - Hostname: service.Hostname, - Image: service.Image, - Interactive: false, - Links: nil, - LinuxParameters: toLinuxParameters(service), - LogConfiguration: logConfiguration, - MemoryReservation: memReservation, - MountPoints: mounts, - Name: service.Name, - PortMappings: toPortMappings(service.Ports), - Privileged: service.Privileged, - PseudoTerminal: service.Tty, - ReadonlyRootFilesystem: service.ReadOnly, - RepositoryCredentials: credential, - ResourceRequirements: toTaskResourceRequirements(reservations), - StartTimeout: 0, - StopTimeout: durationToInt(service.StopGracePeriod), - SystemControls: toSystemControls(service.Sysctls), - Ulimits: toUlimits(service.Ulimits), - User: service.User, - VolumesFrom: nil, - WorkingDirectory: service.WorkingDir, - }) - - launchType := ecsapi.LaunchTypeFargate - if requireEC2(service) { - launchType = ecsapi.LaunchTypeEc2 - } - - return &ecs.TaskDefinition{ - ContainerDefinitions: containers, - Cpu: cpu, - Family: fmt.Sprintf("%s-%s", project.Name, service.Name), - IpcMode: service.Ipc, - Memory: mem, - NetworkMode: ecsapi.NetworkModeAwsvpc, // FIXME could be set by service.NetworkMode, Fargate only supports network mode ‘awsvpc’. - PidMode: service.Pid, - PlacementConstraints: toPlacementConstraints(service.Deploy), - ProxyConfiguration: nil, - RequiresCompatibilities: []string{ - launchType, - }, - Volumes: volumes, - }, nil -} - -func toTaskResourceRequirements(reservations *types.Resource) []ecs.TaskDefinition_ResourceRequirement { - if reservations == nil { - return nil - } - var requirements []ecs.TaskDefinition_ResourceRequirement - for _, r := range reservations.GenericResources { - if r.DiscreteResourceSpec.Kind == "gpus" { - requirements = append(requirements, ecs.TaskDefinition_ResourceRequirement{ - Type: ecsapi.ResourceTypeGpu, - Value: fmt.Sprint(r.DiscreteResourceSpec.Value), - }) - } - } - for _, r := range reservations.Devices { - hasGpuCap := false - for _, c := range r.Capabilities { - if c == "gpu" { - hasGpuCap = true - break - } - } - if hasGpuCap { - count := r.Count - if count <= 0 { - count = 1 - } - requirements = append(requirements, ecs.TaskDefinition_ResourceRequirement{ - Type: ecsapi.ResourceTypeGpu, - Value: fmt.Sprint(count), - }) - } - } - return requirements -} - -func createSecretsSideCar(project *types.Project, service types.ServiceConfig, logConfiguration *ecs.TaskDefinition_LogConfiguration) ( - ecs.TaskDefinition_Volume, - ecs.TaskDefinition_MountPoint, - ecs.TaskDefinition_ContainerDefinition, - error) { - initContainerName := fmt.Sprintf("%s_Secrets_InitContainer", normalizeResourceName(service.Name)) - secretsVolume := ecs.TaskDefinition_Volume{ - Name: "secrets", - } - secretsMount := ecs.TaskDefinition_MountPoint{ - ContainerPath: "/run/secrets/", - ReadOnly: true, - SourceVolume: "secrets", - } - - var ( - args []secrets.Secret - taskSecrets []ecs.TaskDefinition_Secret - ) - for _, s := range service.Secrets { - secretConfig := project.Secrets[s.Source] - if s.Target == "" { - s.Target = s.Source - } - taskSecrets = append(taskSecrets, ecs.TaskDefinition_Secret{ - Name: s.Target, - ValueFrom: secretConfig.Name, - }) - var keys []string - if ext, ok := secretConfig.Extensions[extensionKeys]; ok { - if key, ok := ext.(string); ok { - keys = append(keys, key) - } else { - for _, k := range ext.([]interface{}) { - keys = append(keys, k.(string)) - } - } - } - args = append(args, secrets.Secret{ - Name: s.Target, - Keys: keys, - }) - } - command, err := json.Marshal(args) - if err != nil { - return ecs.TaskDefinition_Volume{}, ecs.TaskDefinition_MountPoint{}, ecs.TaskDefinition_ContainerDefinition{}, err - } - secretsSideCar := ecs.TaskDefinition_ContainerDefinition{ - Name: initContainerName, - Image: secretsInitContainerImage, - Command: []string{string(command)}, - Essential: false, // FIXME this will be ignored, see https://github.com/awslabs/goformation/issues/61#issuecomment-625139607 - LogConfiguration: logConfiguration, - MountPoints: []ecs.TaskDefinition_MountPoint{ - { - ContainerPath: "/run/secrets/", - ReadOnly: false, - SourceVolume: "secrets", - }, - }, - Secrets: taskSecrets, - } - return secretsVolume, secretsMount, secretsSideCar, nil -} - -func createEnvironment(project *types.Project, service types.ServiceConfig) ([]ecs.TaskDefinition_KeyValuePair, error) { - environment := map[string]*string{} - for _, f := range service.EnvFile { - if !filepath.IsAbs(f) { - f = filepath.Join(project.WorkingDir, f) - } - if _, err := os.Stat(f); os.IsNotExist(err) { - return nil, err - } - file, err := os.Open(f) - if err != nil { - return nil, err - } - defer file.Close() // nolint:errcheck - - env, err := godotenv.Parse(file) - if err != nil { - return nil, err - } - for k, v := range env { - environment[k] = &v - } - } - for k, v := range service.Environment { - environment[k] = v - } - - var pairs []ecs.TaskDefinition_KeyValuePair - for k, v := range environment { - name := k - var value string - if v != nil { - value = *v - } - pairs = append(pairs, ecs.TaskDefinition_KeyValuePair{ - Name: name, - Value: value, - }) - } - - //order env keys for idempotence between calls - //to avoid unnecessary resource recreations on CloudFormation - sort.Slice(pairs, func(i, j int) bool { - return pairs[i].Name < pairs[j].Name - }) - - return pairs, nil -} - -func getLogConfiguration(service types.ServiceConfig, project *types.Project) *ecs.TaskDefinition_LogConfiguration { - options := map[string]string{ - "awslogs-region": cloudformation.Ref("AWS::Region"), - "awslogs-group": cloudformation.Ref("LogGroup"), - "awslogs-stream-prefix": project.Name, - } - if service.Logging != nil { - for k, v := range service.Logging.Options { - if strings.HasPrefix(k, "awslogs-") { - options[k] = v - } - } - } - logConfiguration := &ecs.TaskDefinition_LogConfiguration{ - LogDriver: ecsapi.LogDriverAwslogs, - Options: options, - } - return logConfiguration -} - -func toSystemControls(sysctls types.Mapping) []ecs.TaskDefinition_SystemControl { - sys := []ecs.TaskDefinition_SystemControl{} - for k, v := range sysctls { - sys = append(sys, ecs.TaskDefinition_SystemControl{ - Namespace: k, - Value: v, - }) - } - return sys -} - -const miB = 1024 * 1024 - -func toLimits(service types.ServiceConfig) (string, string, error) { - mem, cpu, err := getConfiguredLimits(service) - if err != nil { - return "", "", err - } - if requireEC2(service) { - // just return configured limits expressed in Mb and CPU units - var cpuLimit, memLimit string - if cpu > 0 { - cpuLimit = fmt.Sprint(cpu) - } - if mem > 0 { - memLimit = fmt.Sprint(mem / miB) - } - return cpuLimit, memLimit, nil - } - - // All possible cpu/mem values for Fargate - fargateCPUToMem := map[int64][]types.UnitBytes{ - 256: {512, 1024, 2048}, - 512: {1024, 2048, 3072, 4096}, - 1024: {2048, 3072, 4096, 5120, 6144, 7168, 8192}, - 2048: {4096, 5120, 6144, 7168, 8192, 9216, 10240, 11264, 12288, 13312, 14336, 15360, 16384}, - 4096: {8192, 9216, 10240, 11264, 12288, 13312, 14336, 15360, 16384, 17408, 18432, 19456, 20480, 21504, 22528, 23552, 24576, 25600, 26624, 27648, 28672, 29696, 30720}, - } - cpuLimit := "256" - memLimit := "512" - if mem == 0 && cpu == 0 { - return cpuLimit, memLimit, nil - } - - var cpus []int64 - for k := range fargateCPUToMem { - cpus = append(cpus, k) - } - sort.Slice(cpus, func(i, j int) bool { return cpus[i] < cpus[j] }) - - for _, fargateCPU := range cpus { - options := fargateCPUToMem[fargateCPU] - if cpu <= fargateCPU { - for _, m := range options { - if mem <= m*miB { - cpuLimit = strconv.FormatInt(fargateCPU, 10) - memLimit = strconv.FormatInt(int64(m), 10) - return cpuLimit, memLimit, nil - } - } - } - } - return "", "", fmt.Errorf("the resources requested are not supported by ECS/Fargate") -} - -func getConfiguredLimits(service types.ServiceConfig) (types.UnitBytes, int64, error) { - if service.Deploy == nil { - return 0, 0, nil - } - - limits := service.Deploy.Resources.Limits - if limits == nil { - limits = service.Deploy.Resources.Reservations - } - if limits == nil { - return 0, 0, nil - } - - if limits.NanoCPUs == "" { - return limits.MemoryBytes, 0, nil - } - v, err := opts.ParseCPUs(limits.NanoCPUs) - if err != nil { - return 0, 0, err - } - - return limits.MemoryBytes, v / 1e6, nil -} - -func toContainerReservation(service types.ServiceConfig) (string, int) { - cpuReservation := ".0" - memReservation := 0 - - if service.Deploy == nil { - return cpuReservation, memReservation - } - - reservations := service.Deploy.Resources.Reservations - if reservations == nil { - return cpuReservation, memReservation - } - return reservations.NanoCPUs, int(reservations.MemoryBytes / miB) -} - -func toPlacementConstraints(deploy *types.DeployConfig) []ecs.TaskDefinition_TaskDefinitionPlacementConstraint { - if deploy == nil || deploy.Placement.Constraints == nil || len(deploy.Placement.Constraints) == 0 { - return nil - } - pl := []ecs.TaskDefinition_TaskDefinitionPlacementConstraint{} - for _, c := range deploy.Placement.Constraints { - pl = append(pl, ecs.TaskDefinition_TaskDefinitionPlacementConstraint{ - Expression: c, - Type: "", - }) - } - return pl -} - -func toPortMappings(ports []types.ServicePortConfig) []ecs.TaskDefinition_PortMapping { - if len(ports) == 0 { - return nil - } - m := []ecs.TaskDefinition_PortMapping{} - for _, p := range ports { - m = append(m, ecs.TaskDefinition_PortMapping{ - ContainerPort: int(p.Target), - HostPort: int(p.Published), - Protocol: p.Protocol, - }) - } - return m -} - -func toUlimits(ulimits map[string]*types.UlimitsConfig) []ecs.TaskDefinition_Ulimit { - if len(ulimits) == 0 { - return nil - } - u := []ecs.TaskDefinition_Ulimit{} - for k, v := range ulimits { - u = append(u, ecs.TaskDefinition_Ulimit{ - Name: k, - SoftLimit: v.Soft, - HardLimit: v.Hard, - }) - } - return u -} - -func toLinuxParameters(service types.ServiceConfig) *ecs.TaskDefinition_LinuxParameters { - return &ecs.TaskDefinition_LinuxParameters{ - Capabilities: toKernelCapabilities(service.CapAdd, service.CapDrop), - Devices: nil, - InitProcessEnabled: service.Init != nil && *service.Init, - MaxSwap: 0, - // FIXME SharedMemorySize: service.ShmSize, - Swappiness: 0, - Tmpfs: toTmpfs(service.Tmpfs), - } -} - -func toTmpfs(tmpfs types.StringList) []ecs.TaskDefinition_Tmpfs { - if tmpfs == nil || len(tmpfs) == 0 { - return nil - } - o := []ecs.TaskDefinition_Tmpfs{} - for _, path := range tmpfs { - o = append(o, ecs.TaskDefinition_Tmpfs{ - ContainerPath: path, - Size: 100, // size is required on ECS, unlimited by the compose spec - }) - } - return o -} - -func toKernelCapabilities(add []string, drop []string) *ecs.TaskDefinition_KernelCapabilities { - if len(add) == 0 && len(drop) == 0 { - return nil - } - return &ecs.TaskDefinition_KernelCapabilities{ - Add: add, - Drop: drop, - } - -} - -func toHealthCheck(check *types.HealthCheckConfig) *ecs.TaskDefinition_HealthCheck { - if check == nil { - return nil - } - retries := 0 - if check.Retries != nil { - retries = int(*check.Retries) - } - return &ecs.TaskDefinition_HealthCheck{ - Command: check.Test, - Interval: durationToInt(check.Interval), - Retries: retries, - StartPeriod: durationToInt(check.StartPeriod), - Timeout: durationToInt(check.Timeout), - } -} - -func durationToInt(interval *types.Duration) int { - if interval == nil { - return 0 - } - v := int(time.Duration(*interval).Seconds()) - return v -} - -func toHostEntryPtr(hosts types.HostsList) []ecs.TaskDefinition_HostEntry { - if hosts == nil || len(hosts) == 0 { - return nil - } - e := []ecs.TaskDefinition_HostEntry{} - for _, h := range hosts { - parts := strings.SplitN(h, ":", 2) // FIXME this should be handled by compose-go - e = append(e, ecs.TaskDefinition_HostEntry{ - Hostname: parts[0], - IpAddress: parts[1], - }) - } - return e -} - -func getRepoCredentials(service types.ServiceConfig) *ecs.TaskDefinition_RepositoryCredentials { - if value, ok := service.Extensions[extensionPullCredentials]; ok { - return &ecs.TaskDefinition_RepositoryCredentials{CredentialsParameter: value.(string)} - } - return nil -} - -func requireEC2(s types.ServiceConfig) bool { - return gpuRequirements(s) > 0 -} - -func gpuRequirements(s types.ServiceConfig) int64 { - if deploy := s.Deploy; deploy != nil { - if reservations := deploy.Resources.Reservations; reservations != nil { - for _, resource := range reservations.GenericResources { - if resource.DiscreteResourceSpec.Kind == "gpus" { - return resource.DiscreteResourceSpec.Value - } - } - for _, device := range reservations.Devices { - if len(device.Capabilities) == 1 && device.Capabilities[0] == "gpu" { - return device.Count - } - } - } - } - return 0 -} diff --git a/ecs/doc.go b/ecs/doc.go deleted file mode 100644 index 4fe32bd60..000000000 --- a/ecs/doc.go +++ /dev/null @@ -1,17 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs diff --git a/ecs/down.go b/ecs/down.go deleted file mode 100644 index 3c12ec0e5..000000000 --- a/ecs/down.go +++ /dev/null @@ -1,92 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - - "github.com/pkg/errors" - - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/progress" -) - -func (b *ecsAPIService) Down(ctx context.Context, projectName string, options api.DownOptions) error { - if options.Volumes { - return errors.Wrap(api.ErrNotImplemented, "--volumes option is not supported on ECS") - } - if options.Images != "" { - return errors.Wrap(api.ErrNotImplemented, "--rmi option is not supported on ECS") - } - return progress.Run(ctx, func(ctx context.Context) error { - return b.down(ctx, projectName) - }) -} - -func (b *ecsAPIService) down(ctx context.Context, projectName string) error { - resources, err := b.aws.ListStackResources(ctx, projectName) - if err != nil { - return err - } - - err = resources.apply(awsTypeCapacityProvider, doDelete(ctx, b.aws.DeleteCapacityProvider)) - if err != nil { - return err - } - - err = resources.apply(awsTypeAutoscalingGroup, doDelete(ctx, b.aws.DeleteAutoscalingGroup)) - if err != nil { - return err - } - - previousEvents, err := b.previousStackEvents(ctx, projectName) - if err != nil { - return err - } - - err = b.aws.DeleteStack(ctx, projectName) - if err != nil { - return err - } - return b.WaitStackCompletion(ctx, projectName, stackDelete, previousEvents...) -} - -func (b *ecsAPIService) previousStackEvents(ctx context.Context, project string) ([]string, error) { - events, err := b.aws.DescribeStackEvents(ctx, project) - if err != nil { - return nil, err - } - var previousEvents []string - for _, e := range events { - previousEvents = append(previousEvents, *e.EventId) - } - return previousEvents, nil -} - -func doDelete(ctx context.Context, delete func(ctx context.Context, arn string) error) func(r stackResource) error { - return func(r stackResource) error { - w := progress.ContextWriter(ctx) - w.Event(progress.RemovingEvent(r.LogicalID)) - err := delete(ctx, r.ARN) - if err != nil { - w.Event(progress.ErrorEvent(r.LogicalID)) - return err - } - w.Event(progress.RemovedEvent(r.LogicalID)) - return nil - } -} diff --git a/ecs/e2e/ecs-local/context_test.go b/ecs/e2e/ecs-local/context_test.go deleted file mode 100644 index 7183ec372..000000000 --- a/ecs/e2e/ecs-local/context_test.go +++ /dev/null @@ -1,64 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package main - -import ( - "fmt" - "os" - "testing" - - "gotest.tools/v3/icmd" - - . "github.com/docker/compose-cli/utils/e2e" -) - -const ( - contextName = "ecs-local-test" -) - -var binDir string - -func TestMain(m *testing.M) { - p, cleanup, err := SetupExistingCLI() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - binDir = p - exitCode := m.Run() - cleanup() - os.Exit(exitCode) -} - -func TestCreateContext(t *testing.T) { - c := NewE2eCLI(t, binDir) - - t.Run("create context", func(t *testing.T) { - c.RunDockerCmd("context", "create", "ecs", contextName, "--local-simulation") - res := c.RunDockerCmd("context", "use", contextName) - res.Assert(t, icmd.Expected{Out: contextName}) - res = c.RunDockerCmd("context", "ls") - res.Assert(t, icmd.Expected{Out: contextName + " *"}) - }) - t.Run("delete context", func(t *testing.T) { - res := c.RunDockerCmd("context", "use", "default") - res.Assert(t, icmd.Expected{Out: "default"}) - - res = c.RunDockerCmd("context", "rm", contextName) - res.Assert(t, icmd.Expected{Out: contextName}) - }) -} diff --git a/ecs/e2e/ecs/e2e-ecs_test.go b/ecs/e2e/ecs/e2e-ecs_test.go deleted file mode 100644 index c92c2eca8..000000000 --- a/ecs/e2e/ecs/e2e-ecs_test.go +++ /dev/null @@ -1,195 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package main - -import ( - "fmt" - "io/ioutil" - "net/http" - "os" - "path/filepath" - "strconv" - "strings" - "testing" - "time" - - "gotest.tools/v3/assert" - "gotest.tools/v3/icmd" - "gotest.tools/v3/poll" - - . "github.com/docker/compose-cli/utils/e2e" -) - -var binDir string - -func TestMain(m *testing.M) { - p, cleanup, err := SetupExistingCLI() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - binDir = p - exitCode := m.Run() - cleanup() - os.Exit(exitCode) -} - -func TestSecrets(t *testing.T) { - cmd, testID := setupTest(t) - secretName := "secret" + testID - - t.Run("create secret", func(t *testing.T) { - secretFile := filepath.Join(cmd.BinDir, "secret.txt") - err := ioutil.WriteFile(secretFile, []byte("pass1"), 0644) - assert.Check(t, err == nil) - res := cmd.RunDockerCmd("secret", "create", secretName, secretFile) - assert.Check(t, strings.Contains(res.Stdout(), secretName), res.Stdout()) - }) - - t.Run("list secrets", func(t *testing.T) { - res := cmd.RunDockerCmd("secret", "list") - assert.Check(t, strings.Contains(res.Stdout(), secretName), res.Stdout()) - }) - - t.Run("inspect secret", func(t *testing.T) { - res := cmd.RunDockerCmd("secret", "inspect", secretName) - assert.Check(t, strings.Contains(res.Stdout(), `"Name": "`+secretName+`"`), res.Stdout()) - }) - - t.Run("rm secret", func(t *testing.T) { - cmd.RunDockerCmd("secret", "rm", secretName) - res := cmd.RunDockerCmd("secret", "list") - assert.Check(t, !strings.Contains(res.Stdout(), secretName), res.Stdout()) - }) -} - -func TestCompose(t *testing.T) { - c, stack := setupTest(t) - - t.Run("compose up", func(t *testing.T) { - c.RunDockerCmd("compose", "--project-name", stack, "-f", "./multi_port_secrets.yaml", "up") - }) - - var webURL, wordsURL, secretsURL string - t.Run("compose ps", func(t *testing.T) { - res := c.RunDockerCmd("compose", "--project-name", stack, "ps") - lines := strings.Split(strings.TrimSpace(res.Stdout()), "\n") - - assert.Equal(t, 5, len(lines)) - - var dbDisplayed, wordsDisplayed, webDisplayed, secretsDisplayed bool - for _, line := range lines { - fields := strings.Fields(line) - containerID := fields[0] - serviceName := fields[1] - switch serviceName { - case "db": - dbDisplayed = true - assert.DeepEqual(t, fields, []string{containerID, serviceName, "Running"}) - case "words": - wordsDisplayed = true - assert.Check(t, strings.Contains(fields[3], ":8080->8080/tcp")) - wordsURL = "http://" + strings.Replace(fields[3], "->8080/tcp", "", 1) + "/noun" - case "web": - webDisplayed = true - assert.Check(t, strings.Contains(fields[3], ":80->80/tcp")) - webURL = "http://" + strings.Replace(fields[3], "->80/tcp", "", 1) - case "websecrets": - secretsDisplayed = true - assert.Check(t, strings.Contains(fields[3], ":90->90/tcp")) - secretsURL = "http://" + strings.Replace(fields[3], "->90/tcp", "", 1) - } - } - - assert.Check(t, dbDisplayed) - assert.Check(t, wordsDisplayed) - assert.Check(t, webDisplayed) - assert.Check(t, secretsDisplayed) - }) - - t.Run("compose ls", func(t *testing.T) { - res := c.RunDockerCmd("compose", "ls", "--filter", "name="+stack) - lines := strings.Split(strings.TrimSpace(res.Stdout()), "\n") - - assert.Equal(t, 2, len(lines)) - fields := strings.Fields(lines[1]) - assert.Equal(t, 2, len(fields)) - assert.Equal(t, fields[0], stack) - assert.Equal(t, "Running", fields[1]) - }) - - t.Run("Words GET validating cross service connection", func(t *testing.T) { - out := HTTPGetWithRetry(t, wordsURL, http.StatusOK, 5*time.Second, 300*time.Second) - assert.Assert(t, strings.Contains(out, `"word":`)) - }) - - t.Run("web app GET", func(t *testing.T) { - out := HTTPGetWithRetry(t, webURL, http.StatusOK, 3*time.Second, 120*time.Second) - assert.Assert(t, strings.Contains(out, "Docker Compose demo")) - - out = HTTPGetWithRetry(t, webURL+"/words/noun", http.StatusOK, 2*time.Second, 60*time.Second) - assert.Assert(t, strings.Contains(out, `"word":`)) - }) - - t.Run("access secret", func(t *testing.T) { - out := HTTPGetWithRetry(t, secretsURL+"/mysecret1", http.StatusOK, 3*time.Second, 120*time.Second) - out = strings.ReplaceAll(out, "\r", "") - assert.Equal(t, out, "myPassword1\n") - }) - - t.Run("compose down", func(t *testing.T) { - cmd := c.NewDockerCmd("compose", "--project-name", stack, "down") - res := icmd.StartCmd(cmd) - - checkUp := func(t poll.LogT) poll.Result { - out := res.Combined() - if !strings.Contains(out, "DeleteComplete") { - return poll.Continue("current status \n%s\n", out) - } - return poll.Success() - } - poll.WaitOn(t, checkUp, poll.WithDelay(2*time.Second), poll.WithTimeout(60*time.Second)) - }) -} - -func setupTest(t *testing.T) (*E2eCLI, string) { - startTime := strconv.Itoa(int(time.Now().UnixNano())) - c := NewParallelE2eCLI(t, binDir) - contextName := "e2e" + t.Name() + startTime - stack := contextName - t.Run("create context", func(t *testing.T) { - localTestProfile := os.Getenv("TEST_AWS_PROFILE") - var res *icmd.Result - if localTestProfile != "" { - res = c.RunDockerCmd("context", "create", "ecs", contextName, "--profile", localTestProfile) - } else { - region := os.Getenv("AWS_DEFAULT_REGION") - secretKey := os.Getenv("AWS_SECRET_ACCESS_KEY") - keyID := os.Getenv("AWS_ACCESS_KEY_ID") - assert.Check(t, keyID != "") - assert.Check(t, secretKey != "") - assert.Check(t, region != "") - res = c.RunDockerCmd("context", "create", "ecs", contextName, "--from-env") - } - res.Assert(t, icmd.Expected{Out: "Successfully created ecs context \"" + contextName + "\""}) - res = c.RunDockerCmd("context", "use", contextName) - res.Assert(t, icmd.Expected{Out: contextName}) - res = c.RunDockerCmd("context", "ls") - res.Assert(t, icmd.Expected{Out: contextName + " *"}) - }) - return c, stack -} diff --git a/ecs/e2e/ecs/multi_port_secrets.yaml b/ecs/e2e/ecs/multi_port_secrets.yaml deleted file mode 100644 index 84961fe31..000000000 --- a/ecs/e2e/ecs/multi_port_secrets.yaml +++ /dev/null @@ -1,24 +0,0 @@ -services: - db: - image: gtardif/sentences-db - - words: - image: gtardif/sentences-api - ports: - - "8080:8080" - web: - image: gtardif/sentences-web - ports: - - "80:80" - websecrets: - image: dockerinternal/e2e_test_secret_server - ports: - - "90:90" - environment: - - "PORT=90" - secrets: - - mysecret1 - -secrets: - mysecret1: - file: ./my_secret1.txt diff --git a/ecs/e2e/ecs/my_secret1.txt b/ecs/e2e/ecs/my_secret1.txt deleted file mode 100644 index 73a420f3f..000000000 --- a/ecs/e2e/ecs/my_secret1.txt +++ /dev/null @@ -1 +0,0 @@ -myPassword1 diff --git a/ecs/ec2.go b/ecs/ec2.go deleted file mode 100644 index 8556135c6..000000000 --- a/ecs/ec2.go +++ /dev/null @@ -1,132 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - "encoding/base64" - "fmt" - "strings" - - "github.com/awslabs/goformation/v4/cloudformation" - "github.com/awslabs/goformation/v4/cloudformation/autoscaling" - "github.com/awslabs/goformation/v4/cloudformation/ecs" - "github.com/awslabs/goformation/v4/cloudformation/iam" - "github.com/compose-spec/compose-go/types" -) - -const ( - placementConstraintAMI = "node.ami == " - placementConstraintMachine = "node.machine == " -) - -func (b *ecsAPIService) createCapacityProvider(ctx context.Context, project *types.Project, template *cloudformation.Template, resources awsResources) error { - var ( - ec2 bool - ami string - machineType string - ) - for _, service := range project.Services { - if requireEC2(service) { - ec2 = true - // TODO once we can assign a service to a CapacityProvider, we could run this _per service_ - ami, machineType = getUserDefinedMachine(service) - break - } - } - - if !ec2 { - return nil - } - - if ami == "" { - recommended, err := b.aws.GetParameter(ctx, "/aws/service/ecs/optimized-ami/amazon-linux-2/gpu/recommended") - if err != nil { - return err - } - ami = recommended - } - - if machineType == "" { - t, err := guessMachineType(project) - if err != nil { - return err - } - machineType = t - } - - template.Resources["CapacityProvider"] = &ecs.CapacityProvider{ - AutoScalingGroupProvider: &ecs.CapacityProvider_AutoScalingGroupProvider{ - AutoScalingGroupArn: cloudformation.Ref("AutoscalingGroup"), - ManagedScaling: &ecs.CapacityProvider_ManagedScaling{ - TargetCapacity: 100, - }, - }, - Tags: projectTags(project), - } - - template.Resources["AutoscalingGroup"] = &autoscaling.AutoScalingGroup{ - LaunchConfigurationName: cloudformation.Ref("LaunchConfiguration"), - MaxSize: "10", //TODO - MinSize: "1", - VPCZoneIdentifier: resources.subnetsIDs(), - } - - userData := base64.StdEncoding.EncodeToString([]byte( - fmt.Sprintf("#!/bin/bash\necho ECS_CLUSTER=%s >> /etc/ecs/ecs.config", project.Name))) - - template.Resources["LaunchConfiguration"] = &autoscaling.LaunchConfiguration{ - ImageId: ami, - InstanceType: machineType, - SecurityGroups: resources.allSecurityGroups(), - IamInstanceProfile: cloudformation.Ref("EC2InstanceProfile"), - UserData: userData, - } - - template.Resources["EC2InstanceProfile"] = &iam.InstanceProfile{ - Roles: []string{cloudformation.Ref("EC2InstanceRole")}, - } - - template.Resources["EC2InstanceRole"] = &iam.Role{ - AssumeRolePolicyDocument: ec2InstanceAssumeRolePolicyDocument, - ManagedPolicyArns: []string{ - ecsEC2InstanceRole, - }, - Tags: projectTags(project), - } - - cluster := template.Resources["Cluster"].(*ecs.Cluster) - cluster.CapacityProviders = []string{ - cloudformation.Ref("CapacityProvider"), - } - - return nil -} - -func getUserDefinedMachine(s types.ServiceConfig) (ami string, machineType string) { - if s.Deploy != nil { - for _, s := range s.Deploy.Placement.Constraints { - if strings.HasPrefix(s, placementConstraintAMI) { - ami = s[len(placementConstraintAMI):] - } - if strings.HasPrefix(s, placementConstraintMachine) { - machineType = s[len(placementConstraintMachine):] - } - } - } - return ami, machineType -} diff --git a/ecs/ec2_test.go b/ecs/ec2_test.go deleted file mode 100644 index db476c73d..000000000 --- a/ecs/ec2_test.go +++ /dev/null @@ -1,49 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "testing" - - "github.com/awslabs/goformation/v4/cloudformation/autoscaling" - "gotest.tools/v3/assert" -) - -func TestUserDefinedAMI(t *testing.T) { - template := convertYaml(t, ` -services: - test: - image: "image" - deploy: - placement: - constraints: - - "node.ami == ami123456789" - - "node.machine == t0.femto" - resources: - # devices: - # - capabilities: ["gpu"] - reservations: - memory: 8Gb - generic_resources: - - discrete_resource_spec: - kind: gpus - value: 1 -`, useDefaultVPC) - lc := template.Resources["LaunchConfiguration"].(*autoscaling.LaunchConfiguration) - assert.Check(t, lc.ImageId == "ami123456789") - assert.Check(t, lc.InstanceType == "t0.femto") -} diff --git a/ecs/exec.go b/ecs/exec.go deleted file mode 100644 index 9fc7c479c..000000000 --- a/ecs/exec.go +++ /dev/null @@ -1,29 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - - "github.com/compose-spec/compose-go/types" - - "github.com/docker/compose-cli/pkg/api" -) - -func (b *ecsAPIService) Exec(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) { - return 0, api.ErrNotImplemented -} diff --git a/ecs/gpu.go b/ecs/gpu.go deleted file mode 100644 index a5f0386f0..000000000 --- a/ecs/gpu.go +++ /dev/null @@ -1,253 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "fmt" - "math" - "strconv" - - "github.com/compose-spec/compose-go/types" - "github.com/docker/go-units" -) - -type machine struct { - id string - cpus float64 - memory types.UnitBytes - gpus int64 -} - -type family []machine - -var gpufamily = family{ - { - id: "g4dn.xlarge", - cpus: 4, - memory: 16 * units.GiB, - gpus: 1, - }, - { - id: "g4dn.2xlarge", - cpus: 8, - memory: 32 * units.GiB, - gpus: 1, - }, - { - id: "g4dn.4xlarge", - cpus: 16, - memory: 64 * units.GiB, - gpus: 1, - }, - { - id: "g4dn.8xlarge", - cpus: 32, - memory: 128 * units.GiB, - gpus: 1, - }, - { - id: "g4dn.12xlarge", - cpus: 48, - memory: 192 * units.GiB, - gpus: 4, - }, - { - id: "g4dn.16xlarge", - cpus: 64, - memory: 256 * units.GiB, - gpus: 1, - }, - { - id: "g4dn.metal", - cpus: 96, - memory: 384 * units.GiB, - gpus: 8, - }, -} - -type filterFn func(machine) bool - -func (f family) filter(fn filterFn) family { - var filtered family - for _, machine := range f { - if fn(machine) { - filtered = append(filtered, machine) - } - } - return filtered -} - -func (f family) firstOrError(msg string, args ...interface{}) (machine, error) { - if len(f) == 0 { - return machine{}, fmt.Errorf(msg, args...) - } - return f[0], nil -} - -func guessMachineType(project *types.Project) (string, error) { - // we select a machine type to match all gpus-bound services requirements - // once https://github.com/aws/containers-roadmap/issues/631 is implemented we can define dedicated CapacityProviders per service. - requirements, err := getResourceRequirements(project) - if err != nil { - return "", err - } - - instanceType, err := gpufamily. - filter(func(m machine) bool { - return m.memory > requirements.memory // actual memory available for ECS tasks < total machine memory - }). - filter(func(m machine) bool { - return m.cpus >= requirements.cpus - }). - filter(func(m machine) bool { - return m.gpus >= requirements.gpus - }). - firstOrError("none of the Amazon EC2 G4 instance types meet the requirements for memory:%d cpu:%f gpus:%d", requirements.memory, requirements.cpus, requirements.gpus) - if err != nil { - return "", err - } - return instanceType.id, nil -} - -type resourceRequirements struct { - memory types.UnitBytes - cpus float64 - gpus int64 -} - -func getResourceRequirements(project *types.Project) (*resourceRequirements, error) { - return toResourceRequirementsSlice(project). - filter(func(requirements *resourceRequirements) bool { - return requirements != nil && requirements.gpus != 0 - }). - max() -} - -type eitherRequirementsOrError struct { - requirements []*resourceRequirements - err error -} - -func toResourceRequirementsSlice(project *types.Project) eitherRequirementsOrError { - var requirements []*resourceRequirements - for _, service := range project.Services { - r, err := toResourceRequirements(service) - if err != nil { - return eitherRequirementsOrError{nil, err} - } - requirements = append(requirements, r) - } - return eitherRequirementsOrError{requirements, nil} -} - -func (r eitherRequirementsOrError) filter(fn func(*resourceRequirements) bool) eitherRequirementsOrError { - if r.err != nil { - return r - } - var requirements []*resourceRequirements - for _, req := range r.requirements { - if fn(req) { - requirements = append(requirements, req) - } - } - return eitherRequirementsOrError{requirements, nil} -} - -func toResourceRequirements(service types.ServiceConfig) (*resourceRequirements, error) { - if service.Deploy == nil { - return nil, nil - } - reservations := service.Deploy.Resources.Reservations - if reservations == nil { - return nil, nil - } - - var requiredGPUs int64 - for _, r := range reservations.GenericResources { - if r.DiscreteResourceSpec.Kind == "gpus" { - requiredGPUs = r.DiscreteResourceSpec.Value - break - } - } - - for _, r := range reservations.Devices { - requiresGpus := false - for _, c := range r.Capabilities { - if c == "gpu" { - requiresGpus = true - break - } - } - if requiresGpus { - requiredGPUs = r.Count - if requiredGPUs <= 0 { - requiredGPUs = 1 - } - break - } - } - - var nanocpu float64 - if reservations.NanoCPUs != "" { - v, err := strconv.ParseFloat(reservations.NanoCPUs, 64) - if err != nil { - return nil, err - } - nanocpu = v - } - return &resourceRequirements{ - memory: reservations.MemoryBytes, - cpus: nanocpu, - gpus: requiredGPUs, - }, nil -} - -func (r resourceRequirements) combine(o *resourceRequirements) resourceRequirements { - if o == nil { - return r - } - return resourceRequirements{ - memory: maxUnitBytes(r.memory, o.memory), - cpus: math.Max(r.cpus, o.cpus), - gpus: maxInt64(r.gpus, o.gpus), - } -} - -func (r eitherRequirementsOrError) max() (*resourceRequirements, error) { - if r.err != nil { - return nil, r.err - } - min := resourceRequirements{} - for _, req := range r.requirements { - min = min.combine(req) - } - return &min, nil -} - -func maxInt64(a, b int64) int64 { - if a > b { - return a - } - return b -} - -func maxUnitBytes(a, b types.UnitBytes) types.UnitBytes { - if a > b { - return a - } - return b -} diff --git a/ecs/gpu_test.go b/ecs/gpu_test.go deleted file mode 100644 index c952633bf..000000000 --- a/ecs/gpu_test.go +++ /dev/null @@ -1,115 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "testing" -) - -func TestGuessMachineType(t *testing.T) { - tests := []struct { - name string - yaml string - want string - wantErr bool - }{ - { - name: "1-gpus", - yaml: ` -services: - learning: - image: tensorflow/tensorflow:latest-gpus - deploy: - resources: - reservations: - generic_resources: - - discrete_resource_spec: - kind: gpus - value: 1 -`, - want: "g4dn.xlarge", - wantErr: false, - }, - { - name: "4-gpus", - yaml: ` -services: - learning: - image: tensorflow/tensorflow:latest-gpus - deploy: - resources: - reservations: - generic_resources: - - discrete_resource_spec: - kind: gpus - value: 4 -`, - want: "g4dn.12xlarge", - wantErr: false, - }, - { - name: "1-gpus, high-memory", - yaml: ` -services: - learning: - image: tensorflow/tensorflow:latest-gpus - deploy: - resources: - reservations: - memory: 300Gb - generic_resources: - - discrete_resource_spec: - kind: gpus - value: 2 -`, - want: "g4dn.metal", - wantErr: false, - }, - { - name: "1-gpus, high-cpu", - yaml: ` -services: - learning: - image: tensorflow/tensorflow:latest-gpus - deploy: - resources: - reservations: - memory: 32Gb - cpus: "32" - generic_resources: - - discrete_resource_spec: - kind: gpus - value: 2 -`, - want: "g4dn.12xlarge", - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - project := loadConfig(t, tt.yaml) - got, err := guessMachineType(project) - if (err != nil) != tt.wantErr { - t.Errorf("guessMachineType() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("guessMachineType() got = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/ecs/iam.go b/ecs/iam.go deleted file mode 100644 index f6b80ed24..000000000 --- a/ecs/iam.go +++ /dev/null @@ -1,110 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "fmt" - - "github.com/awslabs/goformation/v4/cloudformation" -) - -const ( - ecsTaskExecutionPolicy = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" - ecrReadOnlyPolicy = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" - ecsEC2InstanceRole = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" - - actionGetSecretValue = "secretsmanager:GetSecretValue" - actionGetParameters = "ssm:GetParameters" - actionDecrypt = "kms:Decrypt" - actionAutoScaling = "application-autoscaling:*" - actionGetMetrics = "cloudwatch:GetMetricStatistics" - actionDescribeService = "ecs:DescribeServices" - actionUpdateService = "ecs:UpdateService" -) - -var ( - ecsTaskAssumeRolePolicyDocument = policyDocument("ecs-tasks.amazonaws.com") - ec2InstanceAssumeRolePolicyDocument = policyDocument("ec2.amazonaws.com") - ausocalingAssumeRolePolicyDocument = policyDocument("application-autoscaling.amazonaws.com") -) - -func policyDocument(service string) PolicyDocument { - return PolicyDocument{ - Version: "2012-10-17", // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html - Statement: []PolicyStatement{ - { - Effect: "Allow", - Principal: PolicyPrincipal{ - Service: service, - }, - Action: []string{"sts:AssumeRole"}, - }, - }, - } -} - -func volumeMountPolicyDocument(volume string, filesystem string) PolicyDocument { - ap := fmt.Sprintf("%sAccessPoint", normalizeResourceName(volume)) - return PolicyDocument{ - Version: "2012-10-17", // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html - Statement: []PolicyStatement{ - { - Effect: "Allow", - Resource: []string{ - filesystem, - }, - Action: []string{ - "elasticfilesystem:ClientMount", - "elasticfilesystem:ClientWrite", - "elasticfilesystem:ClientRootAccess", - }, - Condition: Condition{ - StringEquals: map[string]string{ - "elasticfilesystem:AccessPointArn": cloudformation.Ref(ap), - }, - }, - }, - }, - } -} - -// PolicyDocument describes an IAM policy document -// could alternatively depend on https://github.com/kubernetes-sigs/cluster-api-provider-aws/blob/master/cmd/clusterawsadm/api/iam/v1alpha1/types.go -type PolicyDocument struct { - Version string `json:",omitempty"` - Statement []PolicyStatement `json:",omitempty"` -} - -// PolicyStatement describes an IAM policy statement -type PolicyStatement struct { - Effect string `json:",omitempty"` - Action []string `json:",omitempty"` - Principal PolicyPrincipal `json:",omitempty"` - Resource []string `json:",omitempty"` - Condition Condition `json:",omitempty"` -} - -// PolicyPrincipal describes an IAM policy principal -type PolicyPrincipal struct { - Service string `json:",omitempty"` -} - -// Condition is the map of all conditions in the statement entry. -type Condition struct { - StringEquals map[string]string `json:",omitempty"` - Bool map[string]string `json:",omitempty"` -} diff --git a/ecs/images.go b/ecs/images.go deleted file mode 100644 index d713047e6..000000000 --- a/ecs/images.go +++ /dev/null @@ -1,27 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - - "github.com/docker/compose-cli/pkg/api" -) - -func (b *ecsAPIService) Images(ctx context.Context, projectName string, options api.ImagesOptions) ([]api.ImageSummary, error) { - return nil, api.ErrNotImplemented -} diff --git a/ecs/list.go b/ecs/list.go deleted file mode 100644 index 378ef9721..000000000 --- a/ecs/list.go +++ /dev/null @@ -1,110 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - "fmt" - - "github.com/docker/compose-cli/pkg/api" -) - -func (b *ecsAPIService) List(ctx context.Context, opts api.ListOptions) ([]api.Stack, error) { - stacks, err := b.aws.ListStacks(ctx) - if err != nil { - return nil, err - } - - for _, stack := range stacks { - if stack.Status == api.STARTING { - if err := b.checkStackState(ctx, stack.Name); err != nil { - stack.Status = api.FAILED - stack.Reason = err.Error() - } - } - } - return stacks, nil - -} - -func (b *ecsAPIService) checkStackState(ctx context.Context, name string) error { - resources, err := b.aws.ListStackResources(ctx, name) - if err != nil { - return err - } - svcArns := []string{} - svcNames := map[string]string{} - var cluster string - for _, r := range resources { - if r.Type == "AWS::ECS::Cluster" { - cluster = r.ARN - continue - } - if r.Type == "AWS::ECS::Service" { - if r.ARN == "" { - continue - } - svcArns = append(svcArns, r.ARN) - svcNames[r.ARN] = r.LogicalID - } - } - if len(svcArns) == 0 { - return nil - } - services, err := b.aws.GetServiceTaskDefinition(ctx, cluster, svcArns) - if err != nil { - return err - } - for service, taskDef := range services { - if err := b.checkServiceState(ctx, cluster, service, taskDef); err != nil { - return fmt.Errorf("%s %s", svcNames[service], err.Error()) - } - } - return nil -} - -func (b *ecsAPIService) checkServiceState(ctx context.Context, cluster string, service string, taskdef string) error { - runningTasks, err := b.aws.GetServiceTasks(ctx, cluster, service, false) - if err != nil { - return err - } - if len(runningTasks) > 0 { - return nil - } - stoppedTasks, err := b.aws.GetServiceTasks(ctx, cluster, service, true) - if err != nil { - return err - } - if len(stoppedTasks) == 0 { - return nil - } - // filter tasks by task definition - tasks := []string{} - for _, t := range stoppedTasks { - if t.TaskDefinitionArn != nil && *t.TaskDefinitionArn == taskdef { - tasks = append(tasks, *t.TaskArn) - } - } - if len(tasks) == 0 { - return nil - } - reason, err := b.aws.GetTaskStoppedReason(ctx, cluster, tasks[0]) - if err != nil { - return err - } - return fmt.Errorf("%s", reason) -} diff --git a/ecs/local/backend.go b/ecs/local/backend.go deleted file mode 100644 index 04e20ad79..000000000 --- a/ecs/local/backend.go +++ /dev/null @@ -1,81 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package local - -import ( - "os" - - cliconfig "github.com/docker/cli/cli/config" - "github.com/docker/docker/client" - - "github.com/docker/compose-cli/api/backend" - "github.com/docker/compose-cli/api/cloud" - "github.com/docker/compose-cli/api/containers" - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/api/resources" - "github.com/docker/compose-cli/api/secrets" - "github.com/docker/compose-cli/api/volumes" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/compose" -) - -const backendType = store.EcsLocalSimulationContextType - -func init() { - backend.Register(backendType, backendType, service, getCloudService) -} - -type ecsLocalSimulation struct { - moby *client.Client - compose api.Service -} - -func service() (backend.Service, error) { - apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - if err != nil { - return nil, err - } - - return &ecsLocalSimulation{ - moby: apiClient, - compose: compose.NewComposeService(apiClient, cliconfig.LoadDefaultConfigFile(os.Stderr)), - }, nil -} - -func getCloudService() (cloud.Service, error) { - return ecsLocalSimulation{}, nil -} - -func (e ecsLocalSimulation) ContainerService() containers.Service { - return nil -} - -func (e ecsLocalSimulation) VolumeService() volumes.Service { - return nil -} - -func (e ecsLocalSimulation) SecretsService() secrets.Service { - return nil -} - -func (e ecsLocalSimulation) ComposeService() api.Service { - return e -} - -func (e ecsLocalSimulation) ResourceService() resources.Service { - return nil -} diff --git a/ecs/local/compose.go b/ecs/local/compose.go deleted file mode 100644 index 8bc21ba0f..000000000 --- a/ecs/local/compose.go +++ /dev/null @@ -1,216 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package local - -import ( - "context" - "encoding/json" - "fmt" - "os" - "path/filepath" - - "github.com/aws/aws-sdk-go/aws" - "github.com/compose-spec/compose-go/types" - "github.com/pkg/errors" - "github.com/sanathkr/go-yaml" - - "github.com/docker/compose-cli/pkg/api" -) - -func (e ecsLocalSimulation) Build(ctx context.Context, project *types.Project, options api.BuildOptions) error { - return e.compose.Build(ctx, project, options) -} - -func (e ecsLocalSimulation) Push(ctx context.Context, project *types.Project, options api.PushOptions) error { - return e.compose.Push(ctx, project, options) -} - -func (e ecsLocalSimulation) Pull(ctx context.Context, project *types.Project, options api.PullOptions) error { - return e.compose.Pull(ctx, project, options) -} - -func (e ecsLocalSimulation) Create(ctx context.Context, project *types.Project, opts api.CreateOptions) error { - enhanced, err := e.enhanceForLocalSimulation(project) - if err != nil { - return err - } - - return e.compose.Create(ctx, enhanced, opts) -} - -func (e ecsLocalSimulation) Start(ctx context.Context, project *types.Project, options api.StartOptions) error { - return e.compose.Start(ctx, project, options) -} - -func (e ecsLocalSimulation) Restart(ctx context.Context, project *types.Project, options api.RestartOptions) error { - return e.compose.Restart(ctx, project, options) -} - -func (e ecsLocalSimulation) Stop(ctx context.Context, project *types.Project, options api.StopOptions) error { - return e.compose.Stop(ctx, project, options) -} - -func (e ecsLocalSimulation) Up(ctx context.Context, project *types.Project, options api.UpOptions) error { - return api.ErrNotImplemented -} - -func (e ecsLocalSimulation) Kill(ctx context.Context, project *types.Project, options api.KillOptions) error { - return e.compose.Kill(ctx, project, options) -} - -func (e ecsLocalSimulation) Convert(ctx context.Context, project *types.Project, options api.ConvertOptions) ([]byte, error) { - enhanced, err := e.enhanceForLocalSimulation(project) - if err != nil { - return nil, err - } - - delete(enhanced.Networks, "default") - config := map[string]interface{}{ - "services": enhanced.Services, - "networks": enhanced.Networks, - "volumes": enhanced.Volumes, - "secrets": enhanced.Secrets, - "configs": enhanced.Configs, - } - switch options.Format { - case "json": - return json.MarshalIndent(config, "", " ") - case "yaml": - return yaml.Marshal(config) - default: - return nil, fmt.Errorf("unsupported format %q", options) - } - -} - -func (e ecsLocalSimulation) enhanceForLocalSimulation(project *types.Project) (*types.Project, error) { - project.Networks["credentials_network"] = types.NetworkConfig{ - Name: "credentials_network", - Driver: "bridge", - Ipam: types.IPAMConfig{ - Config: []*types.IPAMPool{ - { - Subnet: "169.254.170.0/24", - Gateway: "169.254.170.1", - }, - }, - }, - } - - // On Windows, this directory can be found at "%UserProfile%\.aws" - home, err := os.UserHomeDir() - if err != nil { - return nil, err - } - - for i, service := range project.Services { - service.Networks["credentials_network"] = &types.ServiceNetworkConfig{ - Ipv4Address: fmt.Sprintf("169.254.170.%d", i+3), - } - if service.DependsOn == nil { - service.DependsOn = types.DependsOnConfig{} - } - service.DependsOn["ecs-local-endpoints"] = types.ServiceDependency{ - Condition: types.ServiceConditionStarted, - } - service.Environment["AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"] = aws.String("/creds") - service.Environment["ECS_CONTAINER_METADATA_URI"] = aws.String("http://169.254.170.2/v3") - project.Services[i] = service - } - - project.Services = append(project.Services, types.ServiceConfig{ - Name: "ecs-local-endpoints", - Image: "amazon/amazon-ecs-local-container-endpoints", - Volumes: []types.ServiceVolumeConfig{ - { - Type: types.VolumeTypeBind, - Source: "/var/run", - Target: "/var/run", - }, - { - Type: types.VolumeTypeBind, - Source: filepath.Join(home, ".aws"), - Target: "/home/.aws", - }, - }, - Environment: map[string]*string{ - "HOME": aws.String("/home"), - "AWS_PROFILE": aws.String("default"), - }, - Networks: map[string]*types.ServiceNetworkConfig{ - "credentials_network": { - Ipv4Address: "169.254.170.2", - }, - }, - }) - return project, nil -} - -func (e ecsLocalSimulation) Down(ctx context.Context, projectName string, options api.DownOptions) error { - options.RemoveOrphans = true - return e.compose.Down(ctx, projectName, options) -} - -func (e ecsLocalSimulation) Logs(ctx context.Context, projectName string, consumer api.LogConsumer, options api.LogOptions) error { - return e.compose.Logs(ctx, projectName, consumer, options) -} - -func (e ecsLocalSimulation) Ps(ctx context.Context, projectName string, options api.PsOptions) ([]api.ContainerSummary, error) { - return e.compose.Ps(ctx, projectName, options) -} -func (e ecsLocalSimulation) List(ctx context.Context, opts api.ListOptions) ([]api.Stack, error) { - return e.compose.List(ctx, opts) -} -func (e ecsLocalSimulation) RunOneOffContainer(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) { - return 0, errors.Wrap(api.ErrNotImplemented, "use docker-compose run") -} - -func (e ecsLocalSimulation) Remove(ctx context.Context, project *types.Project, options api.RemoveOptions) error { - return e.compose.Remove(ctx, project, options) -} - -func (e ecsLocalSimulation) Exec(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) { - return 0, api.ErrNotImplemented -} - -func (e ecsLocalSimulation) Copy(ctx context.Context, project *types.Project, opts api.CopyOptions) error { - return e.compose.Copy(ctx, project, opts) -} - -func (e ecsLocalSimulation) Pause(ctx context.Context, project string, options api.PauseOptions) error { - return e.compose.Pause(ctx, project, options) -} - -func (e ecsLocalSimulation) UnPause(ctx context.Context, project string, options api.PauseOptions) error { - return e.compose.UnPause(ctx, project, options) -} - -func (e ecsLocalSimulation) Top(ctx context.Context, projectName string, services []string) ([]api.ContainerProcSummary, error) { - return e.compose.Top(ctx, projectName, services) -} - -func (e ecsLocalSimulation) Events(ctx context.Context, project string, options api.EventsOptions) error { - return e.compose.Events(ctx, project, options) -} - -func (e ecsLocalSimulation) Port(ctx context.Context, project string, service string, port int, options api.PortOptions) (string, int, error) { - return "", 0, api.ErrNotImplemented -} - -func (e ecsLocalSimulation) Images(ctx context.Context, projectName string, options api.ImagesOptions) ([]api.ImageSummary, error) { - return nil, api.ErrNotImplemented -} diff --git a/ecs/local/context.go b/ecs/local/context.go deleted file mode 100644 index d6857370e..000000000 --- a/ecs/local/context.go +++ /dev/null @@ -1,43 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package local - -import ( - "context" - - "github.com/docker/compose-cli/api/cloud" - "github.com/docker/compose-cli/ecs" - "github.com/docker/compose-cli/pkg/api" -) - -var _ cloud.Service = ecsLocalSimulation{} - -func (e ecsLocalSimulation) Login(ctx context.Context, params interface{}) error { - return api.ErrNotImplemented -} - -func (e ecsLocalSimulation) Logout(ctx context.Context) error { - return api.ErrNotImplemented -} - -func (e ecsLocalSimulation) CreateContextData(ctx context.Context, params interface{}) (contextData interface{}, description string, err error) { - opts := params.(ecs.ContextParams) - if opts.Description == "" { - opts.Description = "ECS local endpoints" - } - return struct{}{}, opts.Description, nil -} diff --git a/ecs/logs.go b/ecs/logs.go deleted file mode 100644 index 4018a7fb4..000000000 --- a/ecs/logs.go +++ /dev/null @@ -1,32 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/utils" -) - -func (b *ecsAPIService) Logs(ctx context.Context, projectName string, consumer api.LogConsumer, options api.LogOptions) error { - if len(options.Services) > 0 { - consumer = utils.FilteredLogConsumer(consumer, options.Services) - } - err := b.aws.GetLogs(ctx, projectName, consumer.Log, options.Follow) - return err -} diff --git a/ecs/marshall.go b/ecs/marshall.go deleted file mode 100644 index 2a70b040a..000000000 --- a/ecs/marshall.go +++ /dev/null @@ -1,78 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "encoding/json" - "fmt" - "strings" - - "github.com/awslabs/goformation/v4/cloudformation" - "github.com/sanathkr/go-yaml" -) - -func marshall(template *cloudformation.Template, format string) ([]byte, error) { - var ( - source func() ([]byte, error) - marshal func(in interface{}) ([]byte, error) - unmarshal func(in []byte, out interface{}) error - ) - switch format { - case "yaml": - source = template.YAML - marshal = yaml.Marshal - unmarshal = yaml.Unmarshal - case "json": - source = template.JSON - marshal = func(in interface{}) ([]byte, error) { - return json.MarshalIndent(in, "", " ") - } - unmarshal = json.Unmarshal - default: - return nil, fmt.Errorf("unsupported format %q", format) - } - - raw, err := source() - if err != nil { - return nil, err - } - - var unmarshalled interface{} - if err := unmarshal(raw, &unmarshalled); err != nil { - return nil, fmt.Errorf("invalid JSON: %s", err) - } - - if input, ok := unmarshalled.(map[interface{}]interface{}); ok { - if resources, ok := input["Resources"]; ok { - for _, uresource := range resources.(map[interface{}]interface{}) { - if resource, ok := uresource.(map[interface{}]interface{}); ok { - if resource["Type"] == "AWS::ECS::TaskDefinition" { - properties := resource["Properties"].(map[interface{}]interface{}) - for _, def := range properties["ContainerDefinitions"].([]interface{}) { - containerDefinition := def.(map[interface{}]interface{}) - if strings.HasSuffix(containerDefinition["Name"].(string), "_InitContainer") { - containerDefinition["Essential"] = false - } - } - } - } - } - } - } - - return marshal(unmarshalled) -} diff --git a/ecs/ps.go b/ecs/ps.go deleted file mode 100644 index 8627bb6cb..000000000 --- a/ecs/ps.go +++ /dev/null @@ -1,58 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - - "github.com/docker/compose-cli/pkg/api" -) - -func (b *ecsAPIService) Ps(ctx context.Context, projectName string, options api.PsOptions) ([]api.ContainerSummary, error) { - cluster, err := b.aws.GetStackClusterID(ctx, projectName) - if err != nil { - return nil, err - } - servicesARN, err := b.aws.ListStackServices(ctx, projectName) - if err != nil { - return nil, err - } - - if len(servicesARN) == 0 { - return nil, nil - } - - summary := []api.ContainerSummary{} - for _, arn := range servicesARN { - service, err := b.aws.DescribeService(ctx, cluster, arn) - if err != nil { - return nil, err - } - - tasks, err := b.aws.DescribeServiceTasks(ctx, cluster, projectName, service.Name) - if err != nil { - return nil, err - } - - for i, t := range tasks { - t.Publishers = service.Publishers - tasks[i] = t - } - summary = append(summary, tasks...) - } - return summary, nil -} diff --git a/ecs/resolv/Dockerfile b/ecs/resolv/Dockerfile deleted file mode 100644 index 154377469..000000000 --- a/ecs/resolv/Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2020 Docker Compose CLI authors - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -FROM golang:1.16 AS builder -WORKDIR $GOPATH/src/github.com/docker/compose-cli/ecs/resolv -COPY . . -RUN GO111MODULE=auto CGO_ENABLED=0 go build -ldflags="-w -s" -o /go/bin/resolv main/main.go - -FROM scratch -COPY --from=builder /go/bin/resolv /resolv -ENTRYPOINT ["/resolv"] diff --git a/ecs/resolv/main/main.go b/ecs/resolv/main/main.go deleted file mode 100644 index 56663c925..000000000 --- a/ecs/resolv/main/main.go +++ /dev/null @@ -1,39 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package main - -import ( - "fmt" - "os" - - "github.com/docker/compose-cli/ecs/resolv" -) - -const resolvconf = "/etc/resolv.conf" - -func main() { - if len(os.Args) < 2 { - fmt.Fprint(os.Stderr, "usage: resolv DOMAIN [DOMAIN]") - os.Exit(1) - } - - err := resolv.SetSearchDomains(resolvconf, os.Args[1:]...) - if err != nil { - fmt.Fprint(os.Stderr, err.Error()) - os.Exit(1) - } -} diff --git a/ecs/resolv/resolv.go b/ecs/resolv/resolv.go deleted file mode 100644 index f516405a0..000000000 --- a/ecs/resolv/resolv.go +++ /dev/null @@ -1,35 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package resolv - -import ( - "os" - "strings" -) - -// SetSearchDomains appends a `search` directive to resolv.conf file for domains -func SetSearchDomains(file string, domains ...string) error { - search := strings.Join(domains, " ") - - f, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - return err - } - defer f.Close() //nolint:errcheck - _, err = f.WriteString("\nsearch " + search) - return err -} diff --git a/ecs/resolv/resolv_test.go b/ecs/resolv/resolv_test.go deleted file mode 100644 index 2dd37e320..000000000 --- a/ecs/resolv/resolv_test.go +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package resolv - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" - - "gotest.tools/v3/assert" - "gotest.tools/v3/fs" - "gotest.tools/v3/golden" -) - -func TestSetDomain(t *testing.T) { - dir := fs.NewDir(t, "resolv").Path() - f := filepath.Join(dir, "resolv.conf") - touch(t, f) - - err := SetSearchDomains(f, "foo", "bar", "zot") - assert.NilError(t, err) - - got, err := ioutil.ReadFile(f) - assert.NilError(t, err) - golden.Assert(t, string(got), "resolv.conf.golden") -} - -func touch(t *testing.T, f string) { - file, err := os.Create(f) - assert.NilError(t, err) - err = file.Close() - assert.NilError(t, err) -} diff --git a/ecs/resolv/testdata/resolv.conf.golden b/ecs/resolv/testdata/resolv.conf.golden deleted file mode 100644 index ed0e2453a..000000000 --- a/ecs/resolv/testdata/resolv.conf.golden +++ /dev/null @@ -1,2 +0,0 @@ - -search foo bar zot \ No newline at end of file diff --git a/ecs/run.go b/ecs/run.go deleted file mode 100644 index 5758fd85d..000000000 --- a/ecs/run.go +++ /dev/null @@ -1,33 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - - "github.com/compose-spec/compose-go/types" - - "github.com/docker/compose-cli/pkg/api" -) - -func (b *ecsAPIService) RunOneOffContainer(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) { - return 0, api.ErrNotImplemented -} - -func (b *ecsAPIService) Remove(ctx context.Context, project *types.Project, options api.RemoveOptions) error { - return api.ErrNotImplemented -} diff --git a/ecs/sdk.go b/ecs/sdk.go deleted file mode 100644 index c0fbb7375..000000000 --- a/ecs/sdk.go +++ /dev/null @@ -1,1251 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "strings" - "time" - - "github.com/docker/compose-cli/api/secrets" - "github.com/docker/compose-cli/internal" - "github.com/docker/compose-cli/pkg/api" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/arn" - "github.com/aws/aws-sdk-go/aws/request" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/autoscaling" - "github.com/aws/aws-sdk-go/service/autoscaling/autoscalingiface" - "github.com/aws/aws-sdk-go/service/cloudformation" - "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" - "github.com/aws/aws-sdk-go/service/cloudwatchlogs" - "github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface" - "github.com/aws/aws-sdk-go/service/ec2" - "github.com/aws/aws-sdk-go/service/ec2/ec2iface" - "github.com/aws/aws-sdk-go/service/ecs" - "github.com/aws/aws-sdk-go/service/ecs/ecsiface" - "github.com/aws/aws-sdk-go/service/efs" - "github.com/aws/aws-sdk-go/service/efs/efsiface" - "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/aws/aws-sdk-go/service/elbv2/elbv2iface" - "github.com/aws/aws-sdk-go/service/iam" - "github.com/aws/aws-sdk-go/service/iam/iamiface" - "github.com/aws/aws-sdk-go/service/s3" - "github.com/aws/aws-sdk-go/service/s3/s3iface" - "github.com/aws/aws-sdk-go/service/s3/s3manager" - "github.com/aws/aws-sdk-go/service/secretsmanager" - "github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface" - "github.com/aws/aws-sdk-go/service/ssm" - "github.com/aws/aws-sdk-go/service/ssm/ssmiface" - "github.com/hashicorp/go-multierror" - "github.com/hashicorp/go-uuid" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -type sdk struct { - ECS ecsiface.ECSAPI - EC2 ec2iface.EC2API - EFS efsiface.EFSAPI - ELB elbv2iface.ELBV2API - CW cloudwatchlogsiface.CloudWatchLogsAPI - IAM iamiface.IAMAPI - CF cloudformationiface.CloudFormationAPI - SM secretsmanageriface.SecretsManagerAPI - SSM ssmiface.SSMAPI - AG autoscalingiface.AutoScalingAPI - S3 s3iface.S3API - uploader *s3manager.Uploader -} - -// sdk implement API -var _ API = sdk{} - -// UserAgentName is the ECS specific user agent used by the cli -const UserAgentName = "Docker CLI" - -func newSDK(sess *session.Session) sdk { - sess.Handlers.Build.PushBack(func(r *request.Request) { - request.AddToUserAgent(r, UserAgentName+"/"+internal.Version) - }) - return sdk{ - ECS: ecs.New(sess), - EC2: ec2.New(sess), - EFS: efs.New(sess), - ELB: elbv2.New(sess), - CW: cloudwatchlogs.New(sess), - IAM: iam.New(sess), - CF: cloudformation.New(sess), - SM: secretsmanager.New(sess), - SSM: ssm.New(sess), - AG: autoscaling.New(sess), - S3: s3.New(sess), - uploader: s3manager.NewUploader(sess), - } -} - -func (s sdk) CheckRequirements(ctx context.Context, region string) error { - settings, err := s.ECS.ListAccountSettingsWithContext(ctx, &ecs.ListAccountSettingsInput{ - EffectiveSettings: aws.Bool(true), - Name: aws.String("serviceLongArnFormat"), - }) - if err != nil { - return err - } - serviceLongArnFormat := settings.Settings[0].Value - if *serviceLongArnFormat != "enabled" { - return fmt.Errorf("this tool requires the \"new ARN resource ID format\".\n"+ - "Check https://%s.console.aws.amazon.com/ecs/home#/settings\n"+ - "Learn more: https://aws.amazon.com/blogs/compute/migrating-your-amazon-ecs-deployment-to-the-new-arn-and-resource-id-format-2", region) - } - return nil -} - -func (s sdk) ResolveCluster(ctx context.Context, nameOrArn string) (awsResource, error) { - logrus.Debug("CheckRequirements if cluster was already created: ", nameOrArn) - clusters, err := s.ECS.DescribeClustersWithContext(ctx, &ecs.DescribeClustersInput{ - Clusters: []*string{aws.String(nameOrArn)}, - }) - if err != nil { - return nil, err - } - if len(clusters.Clusters) == 0 { - return nil, errors.Wrapf(api.ErrNotFound, "cluster %q does not exist", nameOrArn) - } - it := clusters.Clusters[0] - return existingAWSResource{ - arn: aws.StringValue(it.ClusterArn), - id: aws.StringValue(it.ClusterName), - }, nil -} - -func (s sdk) CreateCluster(ctx context.Context, name string) (string, error) { - logrus.Debug("Create cluster ", name) - response, err := s.ECS.CreateClusterWithContext(ctx, &ecs.CreateClusterInput{ClusterName: aws.String(name)}) - if err != nil { - return "", err - } - return *response.Cluster.Status, nil -} - -func (s sdk) CheckVPC(ctx context.Context, vpcID string) error { - logrus.Debug("CheckRequirements on VPC : ", vpcID) - output, err := s.EC2.DescribeVpcAttributeWithContext(ctx, &ec2.DescribeVpcAttributeInput{ - VpcId: aws.String(vpcID), - Attribute: aws.String("enableDnsSupport"), - }) - if err != nil { - return err - } - if !*output.EnableDnsSupport.Value { - return fmt.Errorf("VPC %q doesn't have DNS resolution enabled", vpcID) - } - return nil -} - -func (s sdk) GetDefaultVPC(ctx context.Context) (string, error) { - logrus.Debug("Retrieve default VPC") - vpcs, err := s.EC2.DescribeVpcsWithContext(ctx, &ec2.DescribeVpcsInput{ - Filters: []*ec2.Filter{ - { - Name: aws.String("isDefault"), - Values: []*string{aws.String("true")}, - }, - }, - }) - if err != nil { - return "", err - } - if len(vpcs.Vpcs) == 0 { - return "", fmt.Errorf("account has no default VPC. Set VPC to deploy to using 'x-aws-vpc'") - } - return *vpcs.Vpcs[0].VpcId, nil -} - -func (s sdk) GetSubNets(ctx context.Context, vpcID string) ([]awsResource, error) { - logrus.Debug("Retrieve SubNets") - var ids []awsResource - var token *string - for { - subnets, err := s.EC2.DescribeSubnetsWithContext(ctx, &ec2.DescribeSubnetsInput{ - Filters: []*ec2.Filter{ - { - Name: aws.String("vpc-id"), - Values: []*string{aws.String(vpcID)}, - }, - }, - NextToken: token, - }) - if err != nil { - return nil, err - } - for _, subnet := range subnets.Subnets { - ids = append(ids, existingAWSResource{ - arn: aws.StringValue(subnet.SubnetArn), - id: aws.StringValue(subnet.SubnetId), - }) - } - - if subnets.NextToken == token { - break - } - token = subnets.NextToken - } - return ids, nil -} - -func (s sdk) IsPublicSubnet(ctx context.Context, subNetID string) (bool, error) { - tables, err := s.EC2.DescribeRouteTablesWithContext(ctx, &ec2.DescribeRouteTablesInput{ - Filters: []*ec2.Filter{ - { - Name: aws.String("association.subnet-id"), - Values: []*string{aws.String(subNetID)}, - }, - }, - }) - if err != nil { - return false, err - } - if len(tables.RouteTables) == 0 { - // If a subnet is not explicitly associated with any route table, it is implicitly associated with the main route table. - // https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-route-tables.html - return true, nil - } - for _, routeTable := range tables.RouteTables { - for _, route := range routeTable.Routes { - if aws.StringValue(route.State) != "active" { - continue - } - if strings.HasPrefix(aws.StringValue(route.GatewayId), "igw-") { - // Connected to an internet Gateway - return true, nil - } - } - } - return false, nil -} - -func (s sdk) GetRoleArn(ctx context.Context, name string) (string, error) { - role, err := s.IAM.GetRoleWithContext(ctx, &iam.GetRoleInput{ - RoleName: aws.String(name), - }) - if err != nil { - return "", err - } - return *role.Role.Arn, nil -} - -func (s sdk) StackExists(ctx context.Context, name string) (bool, error) { - stacks, err := s.CF.DescribeStacksWithContext(ctx, &cloudformation.DescribeStacksInput{ - StackName: aws.String(name), - }) - if err != nil { - if strings.HasPrefix(err.Error(), fmt.Sprintf("ValidationError: Stack with ID %s does not exist", name)) { - return false, nil - } - return false, nil - } - return len(stacks.Stacks) > 0, nil -} - -type uploadedTemplateFunc func(body *string, url *string) (string, error) - -const cloudformationBytesLimit = 51200 - -func (s sdk) withTemplate(ctx context.Context, name string, template []byte, region string, fn uploadedTemplateFunc) (string, error) { - if len(template) < cloudformationBytesLimit { - return fn(aws.String(string(template)), nil) - } - - key, err := uuid.GenerateUUID() - if err != nil { - return "", err - } - bucket := "com.docker.compose." + key - logrus.Debugf("Create s3 bucket %q to store cloudformation template", bucket) - - var configuration *s3.CreateBucketConfiguration - if region != "us-east-1" { - configuration = &s3.CreateBucketConfiguration{ - LocationConstraint: aws.String(region), - } - } - _, err = s.S3.CreateBucket(&s3.CreateBucketInput{ - Bucket: aws.String(bucket), - CreateBucketConfiguration: configuration, - }) - if err != nil { - return "", err - } - - upload, err := s.uploader.UploadWithContext(ctx, &s3manager.UploadInput{ - Key: aws.String("template.yaml"), - Body: bytes.NewReader(template), - Bucket: aws.String(bucket), - ContentType: aws.String("application/x-yaml"), - Tagging: aws.String(name), - }) - - if err != nil { - return "", err - } - - defer func() { - _, err := s.S3.DeleteObjectWithContext(ctx, &s3.DeleteObjectInput{ - Bucket: aws.String(bucket), - Key: aws.String("template.yaml"), - VersionId: upload.VersionID, - }) - if err != nil { - logrus.Warnf("Failed to remove S3 bucket: %s", err) - } - _, err = s.S3.DeleteBucketWithContext(ctx, &s3.DeleteBucketInput{ - Bucket: aws.String(bucket), - }) - if err != nil { - logrus.Warnf("Failed to remove S3 bucket: %s", err) - } - }() - - return fn(nil, aws.String(upload.Location)) -} - -func (s sdk) CreateStack(ctx context.Context, name string, region string, template []byte) error { - logrus.Debug("Create CloudFormation stack") - - stackID, err := s.withTemplate(ctx, name, template, region, func(body *string, url *string) (string, error) { - stack, err := s.CF.CreateStackWithContext(ctx, &cloudformation.CreateStackInput{ - OnFailure: aws.String("DELETE"), - StackName: aws.String(name), - TemplateBody: body, - TemplateURL: url, - TimeoutInMinutes: nil, - Capabilities: []*string{ - aws.String(cloudformation.CapabilityCapabilityIam), - }, - Tags: []*cloudformation.Tag{ - { - Key: aws.String(api.ProjectLabel), - Value: aws.String(name), - }, - }, - }) - if err != nil { - return "", err - } - return aws.StringValue(stack.StackId), nil - }) - logrus.Debugf("Stack %s created", stackID) - return err -} - -func (s sdk) CreateChangeSet(ctx context.Context, name string, region string, template []byte) (string, error) { - logrus.Debug("Create CloudFormation Changeset") - update := fmt.Sprintf("Update%s", time.Now().Format("2006-01-02-15-04-05")) - - changeset, err := s.withTemplate(ctx, name, template, region, func(body *string, url *string) (string, error) { - changeset, err := s.CF.CreateChangeSetWithContext(ctx, &cloudformation.CreateChangeSetInput{ - ChangeSetName: aws.String(update), - ChangeSetType: aws.String(cloudformation.ChangeSetTypeUpdate), - StackName: aws.String(name), - TemplateBody: body, - TemplateURL: url, - Capabilities: []*string{ - aws.String(cloudformation.CapabilityCapabilityIam), - }, - }) - if err != nil { - return "", err - } - return aws.StringValue(changeset.Id), err - }) - if err != nil { - return "", err - } - - // we have to WaitUntilChangeSetCreateComplete even this in fail with error `ResourceNotReady` - // so that we can invoke DescribeChangeSet to check status, and then we can know about the actual creation failure cause. - s.CF.WaitUntilChangeSetCreateCompleteWithContext(ctx, &cloudformation.DescribeChangeSetInput{ // nolint:errcheck - ChangeSetName: aws.String(changeset), - }) - - desc, err := s.CF.DescribeChangeSetWithContext(ctx, &cloudformation.DescribeChangeSetInput{ - ChangeSetName: aws.String(update), - StackName: aws.String(name), - }) - if aws.StringValue(desc.Status) == "FAILED" { - return changeset, fmt.Errorf(aws.StringValue(desc.StatusReason)) - } - - return changeset, err -} - -func (s sdk) UpdateStack(ctx context.Context, changeset string) error { - desc, err := s.CF.DescribeChangeSetWithContext(ctx, &cloudformation.DescribeChangeSetInput{ - ChangeSetName: aws.String(changeset), - }) - if err != nil { - return err - } - - if strings.HasPrefix(aws.StringValue(desc.StatusReason), "The submitted information didn't contain changes.") { - return nil - } - - _, err = s.CF.ExecuteChangeSet(&cloudformation.ExecuteChangeSetInput{ - ChangeSetName: aws.String(changeset), - }) - return err -} - -const ( - stackCreate = iota - stackUpdate - stackDelete -) - -func (s sdk) WaitStackComplete(ctx context.Context, name string, operation int) error { - input := &cloudformation.DescribeStacksInput{ - StackName: aws.String(name), - } - switch operation { - case stackCreate: - return s.CF.WaitUntilStackCreateCompleteWithContext(ctx, input) - case stackDelete: - return s.CF.WaitUntilStackDeleteCompleteWithContext(ctx, input) - default: - return fmt.Errorf("internal error: unexpected stack operation %d", operation) - } -} - -func (s sdk) GetStackID(ctx context.Context, name string) (string, error) { - stacks, err := s.CF.DescribeStacksWithContext(ctx, &cloudformation.DescribeStacksInput{ - StackName: aws.String(name), - }) - if err != nil { - return "", err - } - return *stacks.Stacks[0].StackId, nil -} - -func (s sdk) ListStacks(ctx context.Context) ([]api.Stack, error) { - params := cloudformation.DescribeStacksInput{} - var token *string - var stacks []api.Stack - for { - response, err := s.CF.DescribeStacksWithContext(ctx, ¶ms) - if err != nil { - return nil, err - } - for _, stack := range response.Stacks { - for _, t := range stack.Tags { - if *t.Key == api.ProjectLabel { - status := api.RUNNING - switch aws.StringValue(stack.StackStatus) { - case "CREATE_IN_PROGRESS": - status = api.STARTING - case "DELETE_IN_PROGRESS": - status = api.REMOVING - case "UPDATE_IN_PROGRESS": - status = api.UPDATING - default: - } - stacks = append(stacks, api.Stack{ - ID: aws.StringValue(stack.StackId), - Name: aws.StringValue(stack.StackName), - Status: status, - }) - break - } - } - } - if token == response.NextToken { - return stacks, nil - } - token = response.NextToken - } -} - -func (s sdk) GetStackClusterID(ctx context.Context, stack string) (string, error) { - // Note: could use DescribeStackResource but we only can detect `does not exist` case by matching string error message - var token *string - for { - response, err := s.CF.ListStackResourcesWithContext(ctx, &cloudformation.ListStackResourcesInput{ - StackName: aws.String(stack), - }) - if err != nil { - return "", err - } - for _, r := range response.StackResourceSummaries { - if aws.StringValue(r.ResourceType) == "AWS::ECS::Cluster" { - return aws.StringValue(r.PhysicalResourceId), nil - } - } - if token == response.NextToken { - break - } - token = response.NextToken - } - // stack is using user-provided cluster - res, err := s.CF.GetTemplateSummaryWithContext(ctx, &cloudformation.GetTemplateSummaryInput{ - StackName: aws.String(stack), - }) - if err != nil { - return "", err - } - c := aws.StringValue(res.Metadata) - var m templateMetadata - err = json.Unmarshal([]byte(c), &m) - if err != nil { - return "", err - } - if m.Cluster == "" { - return "", errors.Wrap(api.ErrNotFound, "CloudFormation is missing cluster metadata") - } - - return m.Cluster, nil -} - -type templateMetadata struct { - Cluster string `json:",omitempty"` -} - -func (s sdk) GetServiceTaskDefinition(ctx context.Context, cluster string, serviceArns []string) (map[string]string, error) { - defs := map[string]string{} - - svc := []*string{} - for _, s := range serviceArns { - svc = append(svc, aws.String(s)) - } - for i := 0; i < len(svc); i += 10 { - end := i + 10 - if end > len(svc) { - end = len(svc) - } - chunk := svc[i:end] - services, err := s.ECS.DescribeServicesWithContext(ctx, &ecs.DescribeServicesInput{ - Cluster: aws.String(cluster), - Services: chunk, - }) - if err != nil { - return nil, err - } - for _, s := range services.Services { - defs[aws.StringValue(s.ServiceArn)] = aws.StringValue(s.TaskDefinition) - } - } - return defs, nil -} - -func (s sdk) ListStackServices(ctx context.Context, stack string) ([]string, error) { - arns := []string{} - var nextToken *string - for { - response, err := s.CF.ListStackResourcesWithContext(ctx, &cloudformation.ListStackResourcesInput{ - StackName: aws.String(stack), - NextToken: nextToken, - }) - if err != nil { - return nil, err - } - for _, r := range response.StackResourceSummaries { - if aws.StringValue(r.ResourceType) == "AWS::ECS::Service" { - if r.PhysicalResourceId != nil { - arns = append(arns, aws.StringValue(r.PhysicalResourceId)) - } - } - } - nextToken = response.NextToken - if nextToken == nil { - break - } - } - return arns, nil -} - -func (s sdk) GetServiceTasks(ctx context.Context, cluster string, service string, stopped bool) ([]*ecs.Task, error) { - state := "RUNNING" - if stopped { - state = "STOPPED" - } - var token *string - var tasks []*ecs.Task - for { - response, err := s.ECS.ListTasksWithContext(ctx, &ecs.ListTasksInput{ - Cluster: aws.String(cluster), - ServiceName: aws.String(service), - DesiredStatus: aws.String(state), - }) - if err != nil { - return nil, err - } - if len(response.TaskArns) > 0 { - taskDescriptions, err := s.ECS.DescribeTasksWithContext(ctx, &ecs.DescribeTasksInput{ - Cluster: aws.String(cluster), - Tasks: response.TaskArns, - }) - if err != nil { - return nil, err - } - tasks = append(tasks, taskDescriptions.Tasks...) - } - if token == response.NextToken { - return tasks, nil - } - token = response.NextToken - } -} - -func (s sdk) GetTaskStoppedReason(ctx context.Context, cluster string, taskArn string) (string, error) { - taskDescriptions, err := s.ECS.DescribeTasksWithContext(ctx, &ecs.DescribeTasksInput{ - Cluster: aws.String(cluster), - Tasks: []*string{aws.String(taskArn)}, - }) - if err != nil { - return "", err - } - if len(taskDescriptions.Tasks) == 0 { - return "", nil - } - task := taskDescriptions.Tasks[0] - return fmt.Sprintf( - "%s: %s", - aws.StringValue(task.StopCode), - aws.StringValue(task.StoppedReason)), nil - -} - -func (s sdk) DescribeStackEvents(ctx context.Context, stackID string) ([]*cloudformation.StackEvent, error) { - // Fixme implement Paginator on Events and return as a chan(events) - events := []*cloudformation.StackEvent{} - var nextToken *string - for { - resp, err := s.CF.DescribeStackEventsWithContext(ctx, &cloudformation.DescribeStackEventsInput{ - StackName: aws.String(stackID), - NextToken: nextToken, - }) - if err != nil { - return nil, err - } - - events = append(events, resp.StackEvents...) - if resp.NextToken == nil { - return events, nil - } - nextToken = resp.NextToken - } -} - -func (s sdk) ListStackParameters(ctx context.Context, name string) (map[string]string, error) { - st, err := s.CF.DescribeStacksWithContext(ctx, &cloudformation.DescribeStacksInput{ - NextToken: nil, - StackName: aws.String(name), - }) - if err != nil { - return nil, err - } - parameters := map[string]string{} - for _, parameter := range st.Stacks[0].Parameters { - parameters[aws.StringValue(parameter.ParameterKey)] = aws.StringValue(parameter.ParameterValue) - } - return parameters, nil -} - -type stackResource struct { - LogicalID string - Type string - ARN string - Status string -} - -type stackResourceFn func(r stackResource) error - -type stackResources []stackResource - -func (resources stackResources) apply(awsType string, fn stackResourceFn) error { - var errs *multierror.Error - for _, r := range resources { - if r.Type == awsType { - err := fn(r) - if err != nil { - errs = multierror.Append(err) - } - } - } - return errs.ErrorOrNil() -} - -func (s sdk) ListStackResources(ctx context.Context, name string) (stackResources, error) { - var token *string - var resources stackResources - for { - response, err := s.CF.ListStackResourcesWithContext(ctx, &cloudformation.ListStackResourcesInput{ - StackName: aws.String(name), - }) - if err != nil { - return nil, err - } - - for _, r := range response.StackResourceSummaries { - resources = append(resources, stackResource{ - LogicalID: aws.StringValue(r.LogicalResourceId), - Type: aws.StringValue(r.ResourceType), - ARN: aws.StringValue(r.PhysicalResourceId), - Status: aws.StringValue(r.ResourceStatus), - }) - } - if token == response.NextToken { - return resources, nil - } - token = response.NextToken - } -} - -func (s sdk) DeleteStack(ctx context.Context, name string) error { - logrus.Debug("Delete CloudFormation stack") - _, err := s.CF.DeleteStackWithContext(ctx, &cloudformation.DeleteStackInput{ - StackName: aws.String(name), - }) - return err -} - -func (s sdk) CreateSecret(ctx context.Context, secret secrets.Secret) (string, error) { - logrus.Debug("Create secret " + secret.Name) - var tags []*secretsmanager.Tag - for k, v := range secret.Labels { - tags = []*secretsmanager.Tag{ - { - Key: aws.String(k), - Value: aws.String(v), - }, - } - } - // store the secret content as string - content := string(secret.GetContent()) - response, err := s.SM.CreateSecret(&secretsmanager.CreateSecretInput{ - Name: &secret.Name, - SecretString: &content, - Tags: tags, - }) - if err != nil { - return "", err - } - return aws.StringValue(response.ARN), nil -} - -func (s sdk) InspectSecret(ctx context.Context, id string) (secrets.Secret, error) { - logrus.Debug("Inspect secret " + id) - response, err := s.SM.DescribeSecret(&secretsmanager.DescribeSecretInput{SecretId: &id}) - if err != nil { - return secrets.Secret{}, err - } - tags := map[string]string{} - for _, tag := range response.Tags { - tags[aws.StringValue(tag.Key)] = aws.StringValue(tag.Value) - } - - secret := secrets.Secret{ - ID: aws.StringValue(response.ARN), - Name: aws.StringValue(response.Name), - Labels: tags, - } - return secret, nil -} - -func (s sdk) ListSecrets(ctx context.Context) ([]secrets.Secret, error) { - logrus.Debug("List secrets ...") - var ls []secrets.Secret - var token *string - for { - response, err := s.SM.ListSecrets(&secretsmanager.ListSecretsInput{}) - if err != nil { - return nil, err - } - - for _, sec := range response.SecretList { - - tags := map[string]string{} - for _, tag := range sec.Tags { - tags[*tag.Key] = *tag.Value - } - ls = append(ls, secrets.Secret{ - ID: *sec.ARN, - Name: *sec.Name, - Labels: tags, - }) - } - - if token == response.NextToken { - return ls, nil - } - token = response.NextToken - } -} - -func (s sdk) DeleteSecret(ctx context.Context, id string, recover bool) error { - logrus.Debug("List secrets ...") - force := !recover - _, err := s.SM.DeleteSecret(&secretsmanager.DeleteSecretInput{SecretId: &id, ForceDeleteWithoutRecovery: &force}) - return err -} - -func (s sdk) GetLogs(ctx context.Context, name string, consumer func(container string, service string, message string), follow bool) error { - logGroup := fmt.Sprintf("/docker-compose/%s", name) - var startTime int64 - for { - select { - case <-ctx.Done(): - return nil - default: - var hasMore = true - var token *string - for hasMore { - events, err := s.CW.FilterLogEvents(&cloudwatchlogs.FilterLogEventsInput{ - LogGroupName: aws.String(logGroup), - NextToken: token, - StartTime: aws.Int64(startTime), - }) - if err != nil { - return err - } - if events.NextToken == nil { - hasMore = false - } else { - token = events.NextToken - } - - for _, event := range events.Events { - p := strings.Split(aws.StringValue(event.LogStreamName), "/") - consumer(p[1], p[2], aws.StringValue(event.Message)) - startTime = *event.IngestionTime - } - } - } - if !follow { - return nil - } - time.Sleep(500 * time.Millisecond) - } -} - -func (s sdk) DescribeService(ctx context.Context, cluster string, arn string) (api.ServiceStatus, error) { - services, err := s.ECS.DescribeServicesWithContext(ctx, &ecs.DescribeServicesInput{ - Cluster: aws.String(cluster), - Services: []*string{aws.String(arn)}, - Include: aws.StringSlice([]string{"TAGS"}), - }) - if err != nil { - return api.ServiceStatus{}, err - } - - for _, f := range services.Failures { - return api.ServiceStatus{}, errors.Wrapf(api.ErrNotFound, "can't get service status %s: %s", aws.StringValue(f.Detail), aws.StringValue(f.Reason)) - } - service := services.Services[0] - var name string - for _, t := range service.Tags { - if *t.Key == api.ServiceLabel { - name = aws.StringValue(t.Value) - } - } - if name == "" { - return api.ServiceStatus{}, fmt.Errorf("service %s doesn't have a %s tag", *service.ServiceArn, api.ServiceLabel) - } - targetGroupArns := []string{} - for _, lb := range service.LoadBalancers { - targetGroupArns = append(targetGroupArns, *lb.TargetGroupArn) - } - // getURLwithPortMapping makes 2 queries - // one to get the target groups and another for load balancers - loadBalancers, err := s.getURLWithPortMapping(ctx, targetGroupArns) - if err != nil { - return api.ServiceStatus{}, err - } - return api.ServiceStatus{ - ID: aws.StringValue(service.ServiceName), - Name: name, - Replicas: int(aws.Int64Value(service.RunningCount)), - Desired: int(aws.Int64Value(service.DesiredCount)), - Publishers: loadBalancers, - }, nil -} - -func (s sdk) DescribeServiceTasks(ctx context.Context, cluster string, project string, service string) ([]api.ContainerSummary, error) { - var summary []api.ContainerSummary - familly := fmt.Sprintf("%s-%s", project, service) - var token *string - for { - list, err := s.ECS.ListTasks(&ecs.ListTasksInput{ - Cluster: aws.String(cluster), - Family: aws.String(familly), - LaunchType: nil, - MaxResults: nil, - NextToken: token, - }) - if err != nil { - return nil, err - } - - if len(list.TaskArns) == 0 { - break - } - tasks, err := s.ECS.DescribeTasksWithContext(ctx, &ecs.DescribeTasksInput{ - Cluster: aws.String(cluster), - Include: aws.StringSlice([]string{"TAGS"}), - Tasks: list.TaskArns, - }) - if err != nil { - return nil, err - } - - for _, t := range tasks.Tasks { - var project string - var service string - for _, tag := range t.Tags { - switch aws.StringValue(tag.Key) { - case api.ProjectLabel: - project = aws.StringValue(tag.Value) - case api.ServiceLabel: - service = aws.StringValue(tag.Value) - } - } - - id, err := arn.Parse(aws.StringValue(t.TaskArn)) - if err != nil { - return nil, err - } - - summary = append(summary, api.ContainerSummary{ - ID: id.String(), - Name: id.Resource, - Project: project, - Service: service, - State: strings.Title(strings.ToLower(aws.StringValue(t.LastStatus))), - }) - } - - if list.NextToken == token { - break - } - token = list.NextToken - } - - return summary, nil -} - -func (s sdk) getURLWithPortMapping(ctx context.Context, targetGroupArns []string) ([]api.PortPublisher, error) { - if len(targetGroupArns) == 0 { - return nil, nil - } - groups, err := s.ELB.DescribeTargetGroups(&elbv2.DescribeTargetGroupsInput{ - TargetGroupArns: aws.StringSlice(targetGroupArns), - }) - if err != nil { - return nil, err - } - lbarns := []*string{} - for _, tg := range groups.TargetGroups { - lbarns = append(lbarns, tg.LoadBalancerArns...) - } - - lbs, err := s.ELB.DescribeLoadBalancersWithContext(ctx, &elbv2.DescribeLoadBalancersInput{ - LoadBalancerArns: lbarns, - }) - - if err != nil { - return nil, err - } - filterLB := func(arn *string, lbs []*elbv2.LoadBalancer) *elbv2.LoadBalancer { - if aws.StringValue(arn) == "" { - // load balancer arn is nil/"" - return nil - } - for _, lb := range lbs { - if aws.StringValue(lb.LoadBalancerArn) == aws.StringValue(arn) { - return lb - } - } - return nil - } - loadBalancers := []api.PortPublisher{} - for _, tg := range groups.TargetGroups { - for _, lbarn := range tg.LoadBalancerArns { - lb := filterLB(lbarn, lbs.LoadBalancers) - if lb == nil { - continue - } - loadBalancers = append(loadBalancers, api.PortPublisher{ - URL: fmt.Sprintf("%s:%d", aws.StringValue(lb.DNSName), aws.Int64Value(tg.Port)), - TargetPort: int(aws.Int64Value(tg.Port)), - PublishedPort: int(aws.Int64Value(tg.Port)), - Protocol: strings.ToLower(aws.StringValue(tg.Protocol)), - }) - - } - } - return loadBalancers, nil -} - -func (s sdk) ListTasks(ctx context.Context, cluster string, family string) ([]string, error) { - var token *string - var arns []string - for { - response, err := s.ECS.ListTasksWithContext(ctx, &ecs.ListTasksInput{ - Cluster: aws.String(cluster), - Family: aws.String(family), - }) - if err != nil { - return nil, err - } - for _, arn := range response.TaskArns { - arns = append(arns, *arn) - } - if token == response.NextToken { - return arns, nil - } - token = response.NextToken - } -} - -func (s sdk) GetPublicIPs(ctx context.Context, interfaces ...string) (map[string]string, error) { - var token *string - publicIPs := map[string]string{} - for { - response, err := s.EC2.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{ - NetworkInterfaceIds: aws.StringSlice(interfaces), - }) - if err != nil { - return nil, err - } - for _, interf := range response.NetworkInterfaces { - if interf.Association != nil { - publicIPs[aws.StringValue(interf.NetworkInterfaceId)] = aws.StringValue(interf.Association.PublicIp) - } - } - if token == response.NextToken { - return publicIPs, nil - } - token = response.NextToken - } -} - -func (s sdk) ResolveLoadBalancer(ctx context.Context, nameOrArn string) (awsResource, string, string, []awsResource, error) { - logrus.Debug("Check if LoadBalancer exists: ", nameOrArn) - var arns []*string - var names []*string - if arn.IsARN(nameOrArn) { - arns = append(arns, aws.String(nameOrArn)) - } else { - names = append(names, aws.String(nameOrArn)) - } - - lbs, err := s.ELB.DescribeLoadBalancersWithContext(ctx, &elbv2.DescribeLoadBalancersInput{ - LoadBalancerArns: arns, - Names: names, - }) - if err != nil { - return nil, "", "", nil, err - } - if len(lbs.LoadBalancers) == 0 { - return nil, "", "", nil, errors.Wrapf(api.ErrNotFound, "load balancer %q does not exist", nameOrArn) - } - it := lbs.LoadBalancers[0] - var subNets []awsResource - for _, az := range it.AvailabilityZones { - subNets = append(subNets, existingAWSResource{ - id: aws.StringValue(az.SubnetId), - }) - } - return existingAWSResource{ - arn: aws.StringValue(it.LoadBalancerArn), - id: aws.StringValue(it.LoadBalancerName), - }, aws.StringValue(it.Type), aws.StringValue(it.VpcId), subNets, nil -} - -func (s sdk) GetLoadBalancerURL(ctx context.Context, arn string) (string, error) { - logrus.Debug("Retrieve load balancer URL: ", arn) - lbs, err := s.ELB.DescribeLoadBalancersWithContext(ctx, &elbv2.DescribeLoadBalancersInput{ - LoadBalancerArns: []*string{aws.String(arn)}, - }) - if err != nil { - return "", err - } - dnsName := aws.StringValue(lbs.LoadBalancers[0].DNSName) - if dnsName == "" { - return "", fmt.Errorf("Load balancer %s doesn't have a dns name", aws.StringValue(lbs.LoadBalancers[0].LoadBalancerArn)) - } - return dnsName, nil -} - -func (s sdk) GetParameter(ctx context.Context, name string) (string, error) { - parameter, err := s.SSM.GetParameterWithContext(ctx, &ssm.GetParameterInput{ - Name: aws.String(name), - }) - if err != nil { - return "", err - } - - value := *parameter.Parameter.Value - var ami struct { - SchemaVersion int `json:"schema_version"` - ImageName string `json:"image_name"` - ImageID string `json:"image_id"` - OS string `json:"os"` - ECSRuntimeVersion string `json:"ecs_runtime_verion"` - ECSAgentVersion string `json:"ecs_agent_version"` - } - err = json.Unmarshal([]byte(value), &ami) - if err != nil { - return "", err - } - - return ami.ImageID, nil -} - -func (s sdk) SecurityGroupExists(ctx context.Context, sg string) (bool, error) { - desc, err := s.EC2.DescribeSecurityGroupsWithContext(ctx, &ec2.DescribeSecurityGroupsInput{ - GroupIds: aws.StringSlice([]string{sg}), - }) - if err != nil { - return false, err - } - return len(desc.SecurityGroups) > 0, nil -} - -func (s sdk) DeleteCapacityProvider(ctx context.Context, arn string) error { - _, err := s.ECS.DeleteCapacityProvider(&ecs.DeleteCapacityProviderInput{ - CapacityProvider: aws.String(arn), - }) - return err -} - -func (s sdk) DeleteAutoscalingGroup(ctx context.Context, arn string) error { - _, err := s.AG.DeleteAutoScalingGroup(&autoscaling.DeleteAutoScalingGroupInput{ - AutoScalingGroupName: aws.String(arn), - ForceDelete: aws.Bool(true), - }) - return err -} - -func (s sdk) ResolveFileSystem(ctx context.Context, id string) (awsResource, error) { - desc, err := s.EFS.DescribeFileSystemsWithContext(ctx, &efs.DescribeFileSystemsInput{ - FileSystemId: aws.String(id), - }) - if err != nil { - return nil, err - } - if len(desc.FileSystems) == 0 { - return nil, errors.Wrapf(api.ErrNotFound, "EFS file system %q doesn't exist", id) - } - it := desc.FileSystems[0] - return existingAWSResource{ - arn: aws.StringValue(it.FileSystemArn), - id: aws.StringValue(it.FileSystemId), - }, nil -} - -func (s sdk) ListFileSystems(ctx context.Context, tags map[string]string) ([]awsResource, error) { - var results []awsResource - var token *string - for { - desc, err := s.EFS.DescribeFileSystemsWithContext(ctx, &efs.DescribeFileSystemsInput{ - Marker: token, - }) - if err != nil { - return nil, err - } - for _, filesystem := range desc.FileSystems { - if containsAll(filesystem.Tags, tags) { - results = append(results, existingAWSResource{ - arn: aws.StringValue(filesystem.FileSystemArn), - id: aws.StringValue(filesystem.FileSystemId), - }) - } - } - if desc.NextMarker == token { - return results, nil - } - token = desc.NextMarker - } -} - -func containsAll(tags []*efs.Tag, required map[string]string) bool { -TAGS: - for key, value := range required { - for _, t := range tags { - if aws.StringValue(t.Key) == key && aws.StringValue(t.Value) == value { - continue TAGS - } - } - return false - } - return true -} - -func (s sdk) CreateFileSystem(ctx context.Context, tags map[string]string, options VolumeCreateOptions) (awsResource, error) { - var efsTags []*efs.Tag - for k, v := range tags { - efsTags = append(efsTags, &efs.Tag{ - Key: aws.String(k), - Value: aws.String(v), - }) - } - var ( - k *string - p *string - f *float64 - t *string - ) - if options.ProvisionedThroughputInMibps > 1 { - f = aws.Float64(options.ProvisionedThroughputInMibps) - } - if options.KmsKeyID != "" { - k = aws.String(options.KmsKeyID) - } - if options.PerformanceMode != "" { - p = aws.String(options.PerformanceMode) - } - if options.ThroughputMode != "" { - t = aws.String(options.ThroughputMode) - } - res, err := s.EFS.CreateFileSystemWithContext(ctx, &efs.CreateFileSystemInput{ - Encrypted: aws.Bool(true), - KmsKeyId: k, - PerformanceMode: p, - ProvisionedThroughputInMibps: f, - ThroughputMode: t, - Tags: efsTags, - }) - if err != nil { - return nil, err - } - return existingAWSResource{ - id: aws.StringValue(res.FileSystemId), - arn: aws.StringValue(res.FileSystemArn), - }, nil -} - -func (s sdk) DeleteFileSystem(ctx context.Context, id string) error { - _, err := s.EFS.DeleteFileSystemWithContext(ctx, &efs.DeleteFileSystemInput{ - FileSystemId: aws.String(id), - }) - return err -} diff --git a/ecs/secrets.go b/ecs/secrets.go deleted file mode 100644 index aebc7d6b9..000000000 --- a/ecs/secrets.go +++ /dev/null @@ -1,39 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - - "github.com/docker/compose-cli/api/secrets" -) - -func (b *ecsAPIService) CreateSecret(ctx context.Context, secret secrets.Secret) (string, error) { - return b.aws.CreateSecret(ctx, secret) -} - -func (b *ecsAPIService) InspectSecret(ctx context.Context, id string) (secrets.Secret, error) { - return b.aws.InspectSecret(ctx, id) -} - -func (b *ecsAPIService) ListSecrets(ctx context.Context) ([]secrets.Secret, error) { - return b.aws.ListSecrets(ctx) -} - -func (b *ecsAPIService) DeleteSecret(ctx context.Context, id string, recover bool) error { - return b.aws.DeleteSecret(ctx, id, recover) -} diff --git a/ecs/secrets/Dockerfile b/ecs/secrets/Dockerfile deleted file mode 100644 index 675728eb9..000000000 --- a/ecs/secrets/Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2020 Docker Compose CLI authors - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -FROM golang:1.16 AS builder -WORKDIR $GOPATH/src/github.com/docker/compose-cli/ecs/secrets -COPY . . -RUN GO111MODULE=auto CGO_ENABLED=0 go build -ldflags="-w -s" -o /go/bin/secrets main/main.go - -FROM scratch -COPY --from=builder /go/bin/secrets /secrets -ENTRYPOINT ["/secrets"] diff --git a/ecs/secrets/init.go b/ecs/secrets/init.go deleted file mode 100644 index dd0c58749..000000000 --- a/ecs/secrets/init.go +++ /dev/null @@ -1,105 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package secrets - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path/filepath" -) - -// Secret define sensitive data to be bound as file -type Secret struct { - Name string - Keys []string -} - -// CreateSecretFiles retrieve sensitive data from env and store as plain text a a file in path -func CreateSecretFiles(secret Secret, path string) error { - value, ok := os.LookupEnv(secret.Name) - if !ok { - return fmt.Errorf("%q variable not set", secret.Name) - } - - secrets := filepath.Join(path, secret.Name) - - if len(secret.Keys) == 0 { - // raw Secret - fmt.Printf("inject Secret %q info %s\n", secret.Name, secrets) - return ioutil.WriteFile(secrets, []byte(value), 0444) - } - - var unmarshalled interface{} - err := json.Unmarshal([]byte(value), &unmarshalled) - if err != nil { - return fmt.Errorf("%q Secret is not a valid JSON document: %w", secret.Name, err) - } - - dict, ok := unmarshalled.(map[string]interface{}) - if !ok { - return fmt.Errorf("%q Secret is not a JSON dictionary: %w", secret.Name, err) - } - err = os.MkdirAll(secrets, 0755) - if err != nil { - return err - } - - if contains(secret.Keys, "*") { - var keys []string - for k := range dict { - keys = append(keys, k) - } - secret.Keys = keys - } - - for _, k := range secret.Keys { - path := filepath.Join(secrets, k) - fmt.Printf("inject Secret %q info %s\n", k, path) - - v, ok := dict[k] - if !ok { - return fmt.Errorf("%q Secret has no %q key", secret.Name, k) - } - - var raw []byte - if s, ok := v.(string); ok { - raw = []byte(s) - } else { - raw, err = json.Marshal(v) - if err != nil { - return err - } - } - - err = ioutil.WriteFile(path, raw, 0444) - if err != nil { - return err - } - } - return nil -} - -func contains(keys []string, s string) bool { - for _, k := range keys { - if k == s { - return true - } - } - return false -} diff --git a/ecs/secrets/init_test.go b/ecs/secrets/init_test.go deleted file mode 100644 index 57623a9bd..000000000 --- a/ecs/secrets/init_test.go +++ /dev/null @@ -1,104 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package secrets - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" - - "gotest.tools/v3/assert" - "gotest.tools/v3/fs" -) - -func TestRawSecret(t *testing.T) { - dir := fs.NewDir(t, "secrets").Path() - err := os.Setenv("raw", "something_secret") - assert.NilError(t, err) - defer os.Unsetenv("raw") // nolint:errcheck - - err = CreateSecretFiles(Secret{ - Name: "raw", - Keys: nil, - }, dir) - assert.NilError(t, err) - file, err := ioutil.ReadFile(filepath.Join(dir, "raw")) - assert.NilError(t, err) - content := string(file) - assert.Equal(t, content, "something_secret") -} - -func TestSelectedKeysSecret(t *testing.T) { - dir := fs.NewDir(t, "secrets").Path() - err := os.Setenv("json", ` -{ - "foo": "bar", - "zot": "qix" -}`) - assert.NilError(t, err) - defer os.Unsetenv("json") // nolint:errcheck - - err = CreateSecretFiles(Secret{ - Name: "json", - Keys: []string{"foo"}, - }, dir) - assert.NilError(t, err) - file, err := ioutil.ReadFile(filepath.Join(dir, "json", "foo")) - assert.NilError(t, err) - content := string(file) - assert.Equal(t, content, "bar") - - _, err = os.Stat(filepath.Join(dir, "json", "zot")) - assert.Check(t, os.IsNotExist(err)) -} - -func TestAllKeysSecret(t *testing.T) { - dir := fs.NewDir(t, "secrets").Path() - err := os.Setenv("json", ` -{ - "foo": "bar", - "zot": "qix" -}`) - assert.NilError(t, err) - defer os.Unsetenv("json") // nolint:errcheck - - err = CreateSecretFiles(Secret{ - Name: "json", - Keys: []string{"*"}, - }, dir) - assert.NilError(t, err) - file, err := ioutil.ReadFile(filepath.Join(dir, "json", "foo")) - assert.NilError(t, err) - content := string(file) - assert.Equal(t, content, "bar") - - file, err = ioutil.ReadFile(filepath.Join(dir, "json", "zot")) - assert.NilError(t, err) - content = string(file) - assert.Equal(t, content, "qix") -} - -func TestUnknownSecret(t *testing.T) { - dir := fs.NewDir(t, "secrets").Path() - - err := CreateSecretFiles(Secret{ - Name: "not_set", - Keys: nil, - }, dir) - assert.Check(t, err != nil) -} diff --git a/ecs/secrets/main/main.go b/ecs/secrets/main/main.go deleted file mode 100644 index e8eebd55f..000000000 --- a/ecs/secrets/main/main.go +++ /dev/null @@ -1,49 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package main - -import ( - "encoding/json" - "fmt" - "os" - - "github.com/docker/compose-cli/ecs/secrets" -) - -const secretsFolder = "/run/secrets" - -func main() { - if len(os.Args) != 2 { - fmt.Fprint(os.Stderr, "usage: secrets ") - os.Exit(1) - } - - var input []secrets.Secret - err := json.Unmarshal([]byte(os.Args[1]), &input) - if err != nil { - fmt.Fprint(os.Stderr, err.Error()) - os.Exit(1) - } - - for _, secret := range input { - err := secrets.CreateSecretFiles(secret, secretsFolder) - if err != nil { - fmt.Fprint(os.Stderr, err.Error()) - os.Exit(1) - } - } -} diff --git a/ecs/tags.go b/ecs/tags.go deleted file mode 100644 index 15c52960a..000000000 --- a/ecs/tags.go +++ /dev/null @@ -1,59 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "github.com/awslabs/goformation/v4/cloudformation/tags" - "github.com/compose-spec/compose-go/types" - - "github.com/docker/compose-cli/pkg/api" -) - -func projectTags(project *types.Project) []tags.Tag { - return []tags.Tag{ - { - Key: api.ProjectLabel, - Value: project.Name, - }, - } -} - -func serviceTags(project *types.Project, service types.ServiceConfig) []tags.Tag { - return []tags.Tag{ - { - Key: api.ProjectLabel, - Value: project.Name, - }, - { - Key: api.ServiceLabel, - Value: service.Name, - }, - } -} - -func networkTags(project *types.Project, net types.NetworkConfig) []tags.Tag { - return []tags.Tag{ - { - Key: api.ProjectLabel, - Value: project.Name, - }, - { - Key: api.NetworkLabel, - Value: net.Name, - }, - } -} diff --git a/ecs/testdata/context-by-keys-config.golden b/ecs/testdata/context-by-keys-config.golden deleted file mode 100644 index 6d2318321..000000000 --- a/ecs/testdata/context-by-keys-config.golden +++ /dev/null @@ -1,3 +0,0 @@ -[profile default] -region = eu-west-3 - diff --git a/ecs/testdata/context-by-keys-credentials.golden b/ecs/testdata/context-by-keys-credentials.golden deleted file mode 100644 index 2c69e47ef..000000000 --- a/ecs/testdata/context-by-keys-credentials.golden +++ /dev/null @@ -1,4 +0,0 @@ -[default] -aws_access_key_id = ABCD -aws_secret_access_key = X&123 - diff --git a/ecs/testdata/context-by-profile-config.golden b/ecs/testdata/context-by-profile-config.golden deleted file mode 100644 index e388f9b02..000000000 --- a/ecs/testdata/context-by-profile-config.golden +++ /dev/null @@ -1,3 +0,0 @@ -[profile foo] -region = eu-west-3 - diff --git a/ecs/testdata/context-by-profile-credentials.golden b/ecs/testdata/context-by-profile-credentials.golden deleted file mode 100644 index 7146be5fe..000000000 --- a/ecs/testdata/context-by-profile-credentials.golden +++ /dev/null @@ -1,4 +0,0 @@ -[foo] -aws_access_key_id = ABCD -aws_secret_access_key = X&123 - diff --git a/ecs/testdata/input/envfile b/ecs/testdata/input/envfile deleted file mode 100644 index 6ac867af7..000000000 --- a/ecs/testdata/input/envfile +++ /dev/null @@ -1 +0,0 @@ -FOO=BAR diff --git a/ecs/testdata/input/simple-single-service.yaml b/ecs/testdata/input/simple-single-service.yaml deleted file mode 100644 index cf664a431..000000000 --- a/ecs/testdata/input/simple-single-service.yaml +++ /dev/null @@ -1,5 +0,0 @@ -services: - simple: - image: nginx - ports: - - "80:80" \ No newline at end of file diff --git a/ecs/testdata/invalid_network_mode.yaml b/ecs/testdata/invalid_network_mode.yaml deleted file mode 100644 index 6e3857513..000000000 --- a/ecs/testdata/invalid_network_mode.yaml +++ /dev/null @@ -1,4 +0,0 @@ -services: - simple: - image: nginx - network_mode: bridge \ No newline at end of file diff --git a/ecs/testdata/simple-cloudformation-conversion.golden b/ecs/testdata/simple-cloudformation-conversion.golden deleted file mode 100644 index 7f82eb5f0..000000000 --- a/ecs/testdata/simple-cloudformation-conversion.golden +++ /dev/null @@ -1,208 +0,0 @@ -AWSTemplateFormatVersion: 2010-09-09 -Resources: - CloudMap: - Properties: - Description: Service Map for Docker Compose project TestSimpleConvert - Name: TestSimpleConvert.local - Vpc: vpc-123 - Type: AWS::ServiceDiscovery::PrivateDnsNamespace - Cluster: - Properties: - ClusterName: TestSimpleConvert - Tags: - - Key: com.docker.compose.project - Value: TestSimpleConvert - Type: AWS::ECS::Cluster - Default80Ingress: - Properties: - CidrIp: 0.0.0.0/0 - Description: simple:80/tcp on default network - FromPort: 80 - GroupId: - Ref: DefaultNetwork - IpProtocol: TCP - ToPort: 80 - Type: AWS::EC2::SecurityGroupIngress - DefaultNetwork: - Properties: - GroupDescription: TestSimpleConvert Security Group for default network - Tags: - - Key: com.docker.compose.project - Value: TestSimpleConvert - - Key: com.docker.compose.network - Value: TestSimpleConvert_default - VpcId: vpc-123 - Type: AWS::EC2::SecurityGroup - DefaultNetworkIngress: - Properties: - Description: Allow communication within network default - GroupId: - Ref: DefaultNetwork - IpProtocol: "-1" - SourceSecurityGroupId: - Ref: DefaultNetwork - Type: AWS::EC2::SecurityGroupIngress - LoadBalancer: - Properties: - Scheme: internet-facing - SecurityGroups: - - Ref: DefaultNetwork - Subnets: - - subnet1 - - subnet2 - Tags: - - Key: com.docker.compose.project - Value: TestSimpleConvert - Type: application - Type: AWS::ElasticLoadBalancingV2::LoadBalancer - LogGroup: - Properties: - LogGroupName: /docker-compose/TestSimpleConvert - Type: AWS::Logs::LogGroup - SimpleService: - DependsOn: - - SimpleTCP80Listener - Properties: - Cluster: - Fn::GetAtt: - - Cluster - - Arn - DeploymentConfiguration: - MaximumPercent: 200 - MinimumHealthyPercent: 100 - DeploymentController: - Type: ECS - DesiredCount: 1 - LaunchType: FARGATE - LoadBalancers: - - ContainerName: simple - ContainerPort: 80 - TargetGroupArn: - Ref: SimpleTCP80TargetGroup - NetworkConfiguration: - AwsvpcConfiguration: - AssignPublicIp: ENABLED - SecurityGroups: - - Ref: DefaultNetwork - Subnets: - - subnet1 - - subnet2 - PlatformVersion: 1.4.0 - PropagateTags: SERVICE - SchedulingStrategy: REPLICA - ServiceRegistries: - - RegistryArn: - Fn::GetAtt: - - SimpleServiceDiscoveryEntry - - Arn - Tags: - - Key: com.docker.compose.project - Value: TestSimpleConvert - - Key: com.docker.compose.service - Value: simple - TaskDefinition: - Ref: SimpleTaskDefinition - Type: AWS::ECS::Service - SimpleServiceDiscoveryEntry: - Properties: - Description: '"simple" service discovery entry in Cloud Map' - DnsConfig: - DnsRecords: - - TTL: 60 - Type: A - RoutingPolicy: MULTIVALUE - HealthCheckCustomConfig: - FailureThreshold: 1 - Name: simple - NamespaceId: - Ref: CloudMap - Type: AWS::ServiceDiscovery::Service - SimpleTCP80Listener: - Properties: - DefaultActions: - - ForwardConfig: - TargetGroups: - - TargetGroupArn: - Ref: SimpleTCP80TargetGroup - Type: forward - LoadBalancerArn: - Ref: LoadBalancer - Port: 80 - Protocol: HTTP - Type: AWS::ElasticLoadBalancingV2::Listener - SimpleTCP80TargetGroup: - Properties: - Port: 80 - Protocol: HTTP - Tags: - - Key: com.docker.compose.project - Value: TestSimpleConvert - TargetType: ip - VpcId: vpc-123 - Type: AWS::ElasticLoadBalancingV2::TargetGroup - SimpleTaskDefinition: - Properties: - ContainerDefinitions: - - Command: - - .compute.internal - - TestSimpleConvert.local - Essential: false - Image: docker/ecs-searchdomain-sidecar:1.0 - LogConfiguration: - LogDriver: awslogs - Options: - awslogs-group: - Ref: LogGroup - awslogs-region: - Ref: AWS::Region - awslogs-stream-prefix: TestSimpleConvert - Name: Simple_ResolvConf_InitContainer - - DependsOn: - - Condition: SUCCESS - ContainerName: Simple_ResolvConf_InitContainer - Essential: true - Image: nginx - LinuxParameters: {} - LogConfiguration: - LogDriver: awslogs - Options: - awslogs-group: - Ref: LogGroup - awslogs-region: - Ref: AWS::Region - awslogs-stream-prefix: TestSimpleConvert - Name: simple - PortMappings: - - ContainerPort: 80 - HostPort: 80 - Protocol: tcp - Cpu: "256" - ExecutionRoleArn: - Ref: SimpleTaskExecutionRole - Family: TestSimpleConvert-simple - Memory: "512" - NetworkMode: awsvpc - RequiresCompatibilities: - - FARGATE - Type: AWS::ECS::TaskDefinition - SimpleTaskExecutionRole: - Properties: - AssumeRolePolicyDocument: - Statement: - - Action: - - sts:AssumeRole - Condition: {} - Effect: Allow - Principal: - Service: ecs-tasks.amazonaws.com - Version: 2012-10-17 - ManagedPolicyArns: - - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy - - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly - Tags: - - Key: com.docker.compose.project - Value: TestSimpleConvert - - Key: com.docker.compose.service - Value: simple - Type: AWS::IAM::Role - diff --git a/ecs/top.go b/ecs/top.go deleted file mode 100644 index e1a879659..000000000 --- a/ecs/top.go +++ /dev/null @@ -1,27 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - - "github.com/docker/compose-cli/pkg/api" -) - -func (b *ecsAPIService) Top(ctx context.Context, projectName string, services []string) ([]api.ContainerProcSummary, error) { - return nil, api.ErrNotImplemented -} diff --git a/ecs/up.go b/ecs/up.go deleted file mode 100644 index c3a6f2858..000000000 --- a/ecs/up.go +++ /dev/null @@ -1,145 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - "fmt" - "os" - "os/signal" - "syscall" - - "github.com/compose-spec/compose-go/types" - "github.com/sirupsen/logrus" - - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/progress" -) - -func (b *ecsAPIService) Build(ctx context.Context, project *types.Project, options api.BuildOptions) error { - return api.ErrNotImplemented -} - -func (b *ecsAPIService) Push(ctx context.Context, project *types.Project, options api.PushOptions) error { - return api.ErrNotImplemented -} - -func (b *ecsAPIService) Pull(ctx context.Context, project *types.Project, options api.PullOptions) error { - return api.ErrNotImplemented -} - -func (b *ecsAPIService) Create(ctx context.Context, project *types.Project, opts api.CreateOptions) error { - return api.ErrNotImplemented -} - -func (b *ecsAPIService) Start(ctx context.Context, project *types.Project, options api.StartOptions) error { - return api.ErrNotImplemented -} - -func (b *ecsAPIService) Restart(ctx context.Context, project *types.Project, options api.RestartOptions) error { - return api.ErrNotImplemented -} - -func (b *ecsAPIService) Stop(ctx context.Context, project *types.Project, options api.StopOptions) error { - return api.ErrNotImplemented -} - -func (b *ecsAPIService) Pause(ctx context.Context, project string, options api.PauseOptions) error { - return api.ErrNotImplemented -} - -func (b *ecsAPIService) UnPause(ctx context.Context, project string, options api.PauseOptions) error { - return api.ErrNotImplemented -} - -func (b *ecsAPIService) Events(ctx context.Context, project string, options api.EventsOptions) error { - return api.ErrNotImplemented -} - -func (b *ecsAPIService) Port(ctx context.Context, project string, service string, port int, options api.PortOptions) (string, int, error) { - return "", 0, api.ErrNotImplemented -} - -func (b *ecsAPIService) Copy(ctx context.Context, project *types.Project, options api.CopyOptions) error { - return api.ErrNotImplemented -} - -func (b *ecsAPIService) Up(ctx context.Context, project *types.Project, options api.UpOptions) error { - return progress.Run(ctx, func(ctx context.Context) error { - return b.up(ctx, project, options) - }) -} - -func (b *ecsAPIService) up(ctx context.Context, project *types.Project, options api.UpOptions) error { - logrus.Debugf("deploying on AWS with region=%q", b.Region) - err := b.aws.CheckRequirements(ctx, b.Region) - if err != nil { - return err - } - - template, err := b.Convert(ctx, project, api.ConvertOptions{ - Format: "yaml", - }) - if err != nil { - return err - } - - update, err := b.aws.StackExists(ctx, project.Name) - if err != nil { - return err - } - - var previousEvents []string - if update { - var err error - previousEvents, err = b.previousStackEvents(ctx, project.Name) - if err != nil { - return err - } - } - - operation := stackCreate - if update { - operation = stackUpdate - changeset, err := b.aws.CreateChangeSet(ctx, project.Name, b.Region, template) - if err != nil { - return err - } - err = b.aws.UpdateStack(ctx, changeset) - if err != nil { - return err - } - } else { - err = b.aws.CreateStack(ctx, project.Name, b.Region, template) - if err != nil { - return err - } - } - if options.Start.Attach == nil { - return nil - } - signalChan := make(chan os.Signal, 1) - signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) - go func() { - <-signalChan - fmt.Println("user interrupted deployment. Deleting stack...") - b.Down(ctx, project.Name, api.DownOptions{}) // nolint:errcheck - }() - - err = b.WaitStackCompletion(ctx, project.Name, operation, previousEvents...) - return err -} diff --git a/ecs/volumes.go b/ecs/volumes.go deleted file mode 100644 index 6d8c53097..000000000 --- a/ecs/volumes.go +++ /dev/null @@ -1,155 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - "fmt" - - "github.com/docker/compose-cli/api/volumes" - "github.com/docker/compose-cli/pkg/api" - - "github.com/awslabs/goformation/v4/cloudformation" - "github.com/awslabs/goformation/v4/cloudformation/efs" - "github.com/compose-spec/compose-go/types" - "github.com/pkg/errors" -) - -func (b *ecsAPIService) createNFSMountTarget(project *types.Project, resources awsResources, template *cloudformation.Template) { - for volume := range project.Volumes { - for _, subnet := range resources.subnets { - name := fmt.Sprintf("%sNFSMountTargetOn%s", normalizeResourceName(volume), normalizeResourceName(subnet.ID())) - template.Resources[name] = &efs.MountTarget{ - FileSystemId: resources.filesystems[volume].ID(), - SecurityGroups: resources.allSecurityGroups(), - SubnetId: subnet.ID(), - } - } - } -} - -func (b *ecsAPIService) mountTargets(volume string, resources awsResources) []string { - var refs []string - for _, subnet := range resources.subnets { - refs = append(refs, fmt.Sprintf("%sNFSMountTargetOn%s", normalizeResourceName(volume), normalizeResourceName(subnet.ID()))) - } - return refs -} - -func (b *ecsAPIService) createAccessPoints(project *types.Project, r awsResources, template *cloudformation.Template) { - for name, volume := range project.Volumes { - n := fmt.Sprintf("%sAccessPoint", normalizeResourceName(name)) - - uid := volume.DriverOpts["uid"] - gid := volume.DriverOpts["gid"] - permissions := volume.DriverOpts["permissions"] - path := volume.DriverOpts["root_directory"] - - ap := efs.AccessPoint{ - AccessPointTags: []efs.AccessPoint_AccessPointTag{ - { - Key: api.ProjectLabel, - Value: project.Name, - }, - { - Key: api.VolumeLabel, - Value: name, - }, - { - Key: "Name", - Value: volume.Name, - }, - }, - FileSystemId: r.filesystems[name].ID(), - } - - if uid != "" { - ap.PosixUser = &efs.AccessPoint_PosixUser{ - Uid: uid, - Gid: gid, - } - } - if path != "" { - root := efs.AccessPoint_RootDirectory{ - Path: path, - } - ap.RootDirectory = &root - if uid != "" { - root.CreationInfo = &efs.AccessPoint_CreationInfo{ - OwnerUid: uid, - OwnerGid: gid, - Permissions: permissions, - } - } - } - - template.Resources[n] = &ap - } -} - -// VolumeCreateOptions hold EFS filesystem creation options -type VolumeCreateOptions struct { - KmsKeyID string - PerformanceMode string - ProvisionedThroughputInMibps float64 - ThroughputMode string -} - -type ecsVolumeService struct { - backend *ecsAPIService -} - -func (e ecsVolumeService) List(ctx context.Context) ([]volumes.Volume, error) { - filesystems, err := e.backend.aws.ListFileSystems(ctx, nil) - if err != nil { - return nil, err - } - var vol []volumes.Volume - for _, fs := range filesystems { - vol = append(vol, volumes.Volume{ - ID: fs.ID(), - Description: fs.ARN(), - }) - } - return vol, nil -} - -func (e ecsVolumeService) Create(ctx context.Context, name string, options interface{}) (volumes.Volume, error) { - fs, err := e.backend.aws.CreateFileSystem(ctx, map[string]string{ - "Name": name, - }, options.(VolumeCreateOptions)) - return volumes.Volume{ - ID: fs.ID(), - Description: fs.ARN(), - }, err - -} - -func (e ecsVolumeService) Delete(ctx context.Context, volumeID string, options interface{}) error { - return e.backend.aws.DeleteFileSystem(ctx, volumeID) -} - -func (e ecsVolumeService) Inspect(ctx context.Context, volumeID string) (volumes.Volume, error) { - ok, err := e.backend.aws.ResolveFileSystem(ctx, volumeID) - if ok == nil { - err = errors.Wrapf(api.ErrNotFound, "filesystem %q does not exists", volumeID) - } - return volumes.Volume{ - ID: volumeID, - Description: ok.ARN(), - }, err -} diff --git a/ecs/wait.go b/ecs/wait.go deleted file mode 100644 index 3974035e7..000000000 --- a/ecs/wait.go +++ /dev/null @@ -1,124 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "context" - "fmt" - "sort" - "strings" - "time" - - "github.com/docker/compose-cli/pkg/progress" - - "github.com/aws/aws-sdk-go/aws" - "github.com/iancoleman/strcase" -) - -func (b *ecsAPIService) WaitStackCompletion(ctx context.Context, name string, operation int, ignored ...string) error { //nolint:gocyclo - knownEvents := map[string]struct{}{} - for _, id := range ignored { - knownEvents[id] = struct{}{} - } - - // progress writer - w := progress.ContextWriter(ctx) - // Get the unique Stack ID so we can collect events without getting some from previous deployments with same name - stackID, err := b.aws.GetStackID(ctx, name) - if err != nil { - return err - } - - ticker := time.NewTicker(1 * time.Second) - done := make(chan bool) - go func() { - b.aws.WaitStackComplete(ctx, stackID, operation) //nolint:errcheck - ticker.Stop() - done <- true - }() - - var completed bool - var stackErr error - for !completed { - select { - case <-done: - completed = true - case <-ticker.C: - } - events, err := b.aws.DescribeStackEvents(ctx, stackID) - if err != nil { - return err - } - - sort.Slice(events, func(i, j int) bool { - return events[i].Timestamp.Before(*events[j].Timestamp) - }) - - for _, event := range events { - if _, ok := knownEvents[*event.EventId]; ok { - continue - } - knownEvents[*event.EventId] = struct{}{} - - resource := aws.StringValue(event.LogicalResourceId) - reason := aws.StringValue(event.ResourceStatusReason) - status := aws.StringValue(event.ResourceStatus) - progressStatus := progress.Working - - switch status { - case "CREATE_COMPLETE": - if operation == stackCreate { - progressStatus = progress.Done - } - case "UPDATE_COMPLETE": - if operation == stackUpdate { - progressStatus = progress.Done - } - case "DELETE_COMPLETE": - if operation == stackDelete { - progressStatus = progress.Done - } - default: - if strings.HasSuffix(status, "_FAILED") { - progressStatus = progress.Error - if stackErr == nil { - operation = stackDelete - stackErr = fmt.Errorf(reason) - } - } - } - w.Event(progress.NewEvent(resource, progressStatus, fmt.Sprintf("%s %s", toCamelCase(status), reason))) - } - if operation != stackCreate || stackErr != nil { - continue - } - if err := b.checkStackState(ctx, name); err != nil { - if e := b.aws.DeleteStack(ctx, name); e != nil { - return e - } - stackErr = err - operation = stackDelete - w.Event(progress.ErrorMessageEvent(name, err.Error())) - } - } - - return stackErr -} - -func toCamelCase(status string) string { - return strcase.ToCamel(strings.ToLower(status)) -} diff --git a/ecs/wait_test.go b/ecs/wait_test.go deleted file mode 100644 index a7489655c..000000000 --- a/ecs/wait_test.go +++ /dev/null @@ -1,27 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -import ( - "testing" - - "gotest.tools/v3/assert" -) - -func TestStatusCamelCase(t *testing.T) { - assert.Equal(t, toCamelCase("CREATE_IN_PROGRESS"), "CreateInProgress") -} diff --git a/ecs/x.go b/ecs/x.go deleted file mode 100644 index f85cd3f22..000000000 --- a/ecs/x.go +++ /dev/null @@ -1,34 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package ecs - -const ( - extensionSecurityGroup = "x-aws-securitygroup" - extensionVPC = "x-aws-vpc" - extensionPullCredentials = "x-aws-pull_credentials" - extensionLoadBalancer = "x-aws-loadbalancer" - extensionProtocol = "x-aws-protocol" - extensionCluster = "x-aws-cluster" - extensionKeys = "x-aws-keys" - extensionMinPercent = "x-aws-min_percent" - extensionMaxPercent = "x-aws-max_percent" - extensionRetention = "x-aws-logs_retention" - extensionRole = "x-aws-role" - extensionManagedPolicies = "x-aws-policies" - extensionAutoScaling = "x-aws-autoscaling" - extensionCloudFormation = "x-aws-cloudformation" -) diff --git a/go.mod b/go.mod index c979a473b..bd1d660a8 100644 --- a/go.mod +++ b/go.mod @@ -1,22 +1,9 @@ -module github.com/docker/compose-cli +module github.com/docker/compose/v2 go 1.16 require ( github.com/AlecAivazis/survey/v2 v2.2.3 - github.com/Azure/azure-pipeline-go v0.2.2 // indirect - github.com/Azure/azure-sdk-for-go v48.2.0+incompatible - github.com/Azure/azure-storage-file-go v0.8.0 - github.com/Azure/go-autorest/autorest v0.11.12 - github.com/Azure/go-autorest/autorest/adal v0.9.5 - github.com/Azure/go-autorest/autorest/azure/auth v0.5.3 - github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 - github.com/Azure/go-autorest/autorest/date v0.3.0 - github.com/Azure/go-autorest/autorest/to v0.4.0 - github.com/Azure/go-autorest/autorest/validation v0.2.0 // indirect - github.com/Microsoft/go-winio v0.4.17 - github.com/aws/aws-sdk-go v1.35.33 - github.com/awslabs/goformation/v4 v4.15.6 github.com/buger/goterm v1.0.0 github.com/cnabio/cnab-to-oci v0.3.1-beta1 github.com/compose-spec/compose-go v0.0.0-20210826124222-e6bb854d81e9 @@ -28,23 +15,12 @@ require ( github.com/docker/docker v20.10.7+incompatible github.com/docker/go-connections v0.4.0 github.com/docker/go-units v0.4.0 - github.com/fatih/color v1.9.0 // indirect - github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee // indirect - github.com/gobwas/pool v0.2.0 // indirect - github.com/gobwas/ws v1.0.4 + github.com/gofrs/flock v0.8.0 // indirect github.com/golang/mock v1.5.0 - github.com/golang/protobuf v1.5.2 - github.com/google/go-cmp v0.5.5 github.com/hashicorp/go-multierror v1.1.0 - github.com/hashicorp/go-uuid v1.0.2 github.com/hashicorp/go-version v1.3.0 - github.com/iancoleman/strcase v0.1.2 - github.com/joho/godotenv v1.3.0 github.com/kr/pty v1.1.8 // indirect - github.com/labstack/echo v3.3.10+incompatible - github.com/labstack/gommon v0.3.0 // indirect github.com/mattn/go-colorable v0.1.6 // indirect - github.com/mattn/go-ieproxy v0.0.1 // indirect github.com/mattn/go-isatty v0.0.12 github.com/mattn/go-shellwords v1.0.12 github.com/moby/buildkit v0.8.2-0.20210401015549-df49b648c8bf @@ -53,31 +29,18 @@ require ( github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.0.1 github.com/pkg/errors v0.9.1 - github.com/prometheus/tsdb v0.10.0 - github.com/rogpeppe/go-internal v1.5.2 // indirect github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b + github.com/sergi/go-diff v1.1.0 // indirect github.com/sirupsen/logrus v1.8.1 - github.com/smartystreets/assertions v1.0.0 // indirect github.com/spf13/cobra v1.2.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.7.0 - github.com/valyala/fasttemplate v1.2.1 // indirect - golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 - google.golang.org/grpc v1.38.0 - google.golang.org/protobuf v1.26.0 - gopkg.in/ini.v1 v1.62.0 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b gotest.tools v2.2.0+incompatible gotest.tools/v3 v3.0.3 - helm.sh/helm/v3 v3.6.1-0.20210722154129-5946457ce9ea - k8s.io/api v0.21.0 - k8s.io/apimachinery v0.21.0 - k8s.io/cli-runtime v0.21.0 - k8s.io/client-go v0.21.0 - rsc.io/letsencrypt v0.0.3 // indirect - sigs.k8s.io/kustomize/kyaml v0.10.15 + k8s.io/client-go v0.21.0 // indirect ) // (for buildx) diff --git a/go.sum b/go.sum index c4a0dd672..3d42d43b5 100644 --- a/go.sum +++ b/go.sum @@ -44,75 +44,37 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AlecAivazis/survey/v2 v2.2.3 h1:utJR2X4Ibp2fBxdjalQUiMFf3zfQNjA15YE8+ftlKEs= github.com/AlecAivazis/survey/v2 v2.2.3/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk= -github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= -github.com/Azure/azure-pipeline-go v0.2.2 h1:6oiIS9yaG6XCCzhgAgKFfIWyo4LLCiDhZot6ltoThhY= -github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v19.1.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go v48.2.0+incompatible h1:+t2P1j1r5N6lYgPiiz7ZbEVZFkWjVe9WhHbMm0gg8hw= -github.com/Azure/azure-sdk-for-go v48.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-storage-file-go v0.8.0 h1:OX8DGsleWLUE6Mw4R/OeWEZMvsTIpwN94J59zqKQnTI= -github.com/Azure/azure-storage-file-go v0.8.0/go.mod h1:3w3mufGcMjcOJ3w+4Gs+5wsSgkT7xDwWWqMMIrXtW4c= 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 v10.15.5+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v12.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= -github.com/Azure/go-autorest/autorest v0.11.9/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= -github.com/Azure/go-autorest/autorest v0.11.12 h1:gI8ytXbxMfI+IVbI9mP2JGCTXIuhHLgRlvQ9X4PsnHE= github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= 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 h1:Y3bBUV4rTuxenJJs41HU3qmqsb+auo+a3Lz+PlJPpL0= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= -github.com/Azure/go-autorest/autorest/azure/auth v0.5.3 h1:lZifaPRAk1bqg5vGqreL6F8uLC5V0fDpY8nFvc3boFc= -github.com/Azure/go-autorest/autorest/azure/auth v0.5.3/go.mod h1:4bJZhUhcq8LB20TruwHbAQsmUs2Xh+QR7utuJpLXX3A= -github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 h1:dMOmEJfkLKW/7JsokJqkyoYSgmR08hi9KrhjZb+JALY= -github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= 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 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= -github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= -github.com/Azure/go-autorest/autorest/validation v0.2.0 h1:15vMO4y76dehZSq7pAaOLQxC6dZYsSrj2GQpflyM/L4= -github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/logger v0.2.0 h1:e4RVHVZKC5p6UANLJHkM4OfR1UKZPj8Wt8Pcx+3oqrE= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= -github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= -github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= -github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= -github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= -github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= -github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= -github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= -github.com/Masterminds/squirrel v1.5.0 h1:JukIZisrUXadA9pl3rMkjhiamxiB0cXiu+HGp/Y8cY8= -github.com/Masterminds/squirrel v1.5.0/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= -github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= 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= @@ -134,38 +96,29 @@ github.com/Microsoft/hcsshim v0.8.18/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwT 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/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= 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 h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI= github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= -github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= 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/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apparentlymart/go-cidr v1.0.1/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= @@ -174,21 +127,10 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC 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/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= -github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= -github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= -github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.15.90/go.mod h1:es1KtYUFs7le0xQ3rOihkuoVD90z7D0fR2Qm4S00/gU= -github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/aws/aws-sdk-go v1.35.33 h1:8qPRZqCRok5i7VNN51k/Ky7CuyoXMdSs4mUfKyCqvPw= -github.com/aws/aws-sdk-go v1.35.33/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/awslabs/goformation/v4 v4.15.6 h1:9F0MbtJVSMkuI19G6Fm+qHc1nqScHcOIf+3YRRv+Ohc= -github.com/awslabs/goformation/v4 v4.15.6/go.mod h1:wB5lKZf1J0MYH1Lt4B9w3opqz0uIjP7MMCAcib3QkwA= 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= @@ -204,7 +146,6 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb github.com/bmatcuk/doublestar v1.1.5/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= 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/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/buger/goterm v1.0.0 h1:ZB6uUlY8+sjJyFGzz2WpRqX2XYPeXVgtZAOJMwOsTWM= github.com/buger/goterm v1.0.0/go.mod h1:16STi3LquiscTIHA8SXUNKEa/Cnu4ZHBH8NsCaWgso0= @@ -217,7 +158,6 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0Bsq github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/bugsnag/panicwrap v1.2.0 h1:OzrKrRvXis8qEvOkfcxNcYbOd2O7xXS2nnKMEMABFQA= github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= @@ -228,7 +168,6 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= 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= @@ -237,7 +176,6 @@ github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmE 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/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a/go.mod h1:rzgs2ZOiguV6/NpiDgADjRLPNyZlApIWxKpkT+X8SdY= github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= @@ -254,7 +192,6 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/compose-spec/compose-go v0.0.0-20210826124222-e6bb854d81e9 h1:LiuqF2xtw9Nt8E3svl+r1M8EoRJwejQcNQAGffVKJgY= github.com/compose-spec/compose-go v0.0.0-20210826124222-e6bb854d81e9/go.mod h1:Hnmn5ZCVA3sSBN2urjCZNNIyNqCPayRGH7PmMSaV2Q0= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= @@ -293,7 +230,6 @@ github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7 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.0-rc.3/go.mod h1:kYiJ+LvywDUKzyax6+UKCk5xwQNCfcGR6KsSdShdg5U= github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= github.com/containerd/containerd v1.5.4 h1:uPF0og3ByFzDnaStfiQj3fVGTEtaSNyU+bW7GR/nqGA= github.com/containerd/containerd v1.5.4/go.mod h1:sx18RgvW6ABJ4iYUw7Q5x7bgFOAB9B6G7+yO0XBc4zw= @@ -379,7 +315,6 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= 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= @@ -391,16 +326,12 @@ github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2 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/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= -github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= 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/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= -github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e h1:n81KvOMrLZa+VWHwST7dun9f0G98X3zREHS1ztYzZKU= github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e/go.mod h1:xpWTC2KnJMiDLkoawhsPQcXjvwATEBcbq0xevG2YR9M= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= @@ -409,7 +340,6 @@ github.com/docker/buildx v0.5.2-0.20210422185057-908a856079fc/go.mod h1:T5sa7xGu github.com/docker/cli v0.0.0-20190925022749-754388324470/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli v20.10.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.7+incompatible h1:pv/3NqibQKphWZiAskMzdz8w0PRbtTaEB+f6NwdU7Is= github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/compose-on-kubernetes v0.4.19-0.20190128150448-356b2919c496/go.mod h1:iT2pYfi580XlpaV4KmK0T6+4/9+XoKmk/fhoDod1emE= @@ -422,7 +352,6 @@ github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4Kfc github.com/docker/docker v0.0.0-20200511152416-a93e9eb0e95c/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v1.4.2-0.20180531152204-71cd53e4a197/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v1.4.2-0.20181229214054-f76d6a078d88/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v17.12.0-ce-rc1.0.20200730172259-9f28837c1d93+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.7+incompatible h1:Z6O9Nhsjv+ayUEeI1IojKbYcsGdgYSNqxe1s2MYzUhQ= @@ -459,15 +388,12 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484 h1:pEtiCjIXx3RvGjlUJuCNxNOw0MNblyR9Wi+vJGBFh+8= github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= 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.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= 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= @@ -477,21 +403,10 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= -github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= -github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= -github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= 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= @@ -499,119 +414,52 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/fvbommel/sortorder v1.0.1 h1:dSnXLt4mJYH25uDDGa3biZNQsozaUWDSWeKJ0qqFfzE= github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= -github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7 h1:LofdAjjjqCSXMwLGgOgnE+rdPuvX9DxCqaHwKy7i/ko= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= 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/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= 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-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo= -github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= 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-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 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-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= -github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= -github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= -github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= -github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= -github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= -github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= -github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= -github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= -github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= -github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= -github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/spec v0.19.5 h1:Xm0Ao53uqnk9QE/LlYV5DEU09UAgpliA85QoT9LzqPw= -github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= -github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= -github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= -github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= -github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= -github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= -github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/envy v1.7.1 h1:OQl5ys5MBea7OGCdvPbBJWRgnhC/fGona6QKfvFeau8= -github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= -github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= -github.com/gobuffalo/logger v1.0.1 h1:ZEgyRGgAm4ZAhAO45YXMs5Fp+bzGLESFewzAVBMKuTg= -github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= -github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4= -github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= -github.com/gobuffalo/packr/v2 v2.7.1 h1:n3CIW5T17T8v4GGK5sWXLVWJhCz7b5aNLSxW6gYim4o= -github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs= -github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= 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 v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= 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/godror/godror v0.13.3/go.mod h1:2ouUT4kdhUBk7TAkHWD4SN0CdI0pgEQbo8FVHhbSKWg= github.com/gofrs/flock v0.7.3/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY= github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= github.com/gogo/googleapis v1.3.0/go.mod h1:d+q1s/xVJxZGKWwC/6UfPIF33J+G1Tq4GYv9Y+Tg/EU= github.com/gogo/googleapis v1.4.0 h1:zgVt4UpGxcqVOw97aRGxT4svlcmdK35fynLNctY32zI= @@ -624,7 +472,6 @@ github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP 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-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= 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= @@ -662,12 +509,9 @@ github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE= github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= @@ -716,14 +560,11 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= @@ -733,11 +574,8 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 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/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= -github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= 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= @@ -754,9 +592,7 @@ github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMW github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok= github.com/hanwen/go-fuse/v2 v2.0.4-0.20201208195215-4a458845028b/go.mod h1:0EQM6aH2ctVpvZ6a+onrQ/vaykxh2GH7hy3e13vzTUY= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.3.0/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 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -773,16 +609,12 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX 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-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw= github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 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/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 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= @@ -795,11 +627,6 @@ github.com/hashicorp/uuid v0.0.0-20160311170451-ebb0a03e909c/go.mod h1:fHzc09Uny github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs= -github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= -github.com/iancoleman/strcase v0.1.2 h1:gnomlvw9tnV3ITTAxzKSgTF+8kFWcU/f+TgttpXGz1U= -github.com/iancoleman/strcase v0.1.2/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -810,7 +637,6 @@ github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= 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/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -824,30 +650,19 @@ github.com/jinzhu/now v1.0.0/go.mod h1:oHTiXerJ20+SfYcrdlBO7rzZRJWGwSTQ0iUY2jI6G github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 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/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVzF6no3QaDSMLGLEHtHSBSefs+MgcDWnmhmo= github.com/jmoiron/sqlx v0.0.0-20180124204410-05cef0741ade/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU= -github.com/jmoiron/sqlx v1.3.1 h1:aLN7YINNZ7cYOPK3QC83dbM6KT0NMqVMw961TqrejlE= -github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= -github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 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.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= @@ -882,68 +697,34 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28/go.mod h1:T/T7jsxVqf9k/zYOqbgNAsANsjxTd1Yq3htjDhQ1H0c= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= -github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= -github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= -github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= -github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= -github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= -github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v0.0.0-20180201184707-88edab080323/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= -github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= -github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= -github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 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 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= 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-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= -github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= -github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI= -github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= 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-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-oci8 v0.0.7/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= -github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= -github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 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 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -954,16 +735,10 @@ github.com/miekg/pkcs11 v1.0.3 h1:iMwmD7I5225wv84WxIG/bmxz9AXjWvTWIbM/TYHvWtw= 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/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= -github.com/mitchellh/copystructure v1.1.1 h1:Bp6x9R1Wn16SIz3OfeDr0b7RnCG2OB66Y7PQyC/cvq4= -github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4= 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/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= @@ -972,14 +747,10 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= -github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/buildkit v0.8.2-0.20210401015549-df49b648c8bf h1:dHwWBX8OhYb69qVcT27rFSwzKsn4CRbg0HInQyVh33c= github.com/moby/buildkit v0.8.2-0.20210401015549-df49b648c8bf/go.mod h1:GJcrUlTGFAPlEmPQtbrTsJYn+cy+Jwl7vTZS7jYAoow= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mount v0.1.0/go.mod h1:FVQFLDRWwyBjDTBNQXDlWnSFREqOo3OKX9aqhmeoo74= github.com/moby/sys/mount v0.2.0 h1:WhCW5B355jtxndN5ovugJlMFJawbUODuW8fSnEH6SSM= @@ -992,7 +763,6 @@ github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2J 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-20201110203204-bea5bbe245bf/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= -github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= 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= @@ -1002,8 +772,6 @@ github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lN github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= -github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= @@ -1013,26 +781,13 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m 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/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= -github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= 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/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= -github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= 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= @@ -1043,8 +798,6 @@ github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M= -github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 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.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -1052,8 +805,6 @@ github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa 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.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -1084,32 +835,17 @@ github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mo github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= github.com/opencontainers/selinux v1.8.0 h1:+77ba4ar4jsCbL1GLbFL8fFM57w6suPfSS9PDLDY7KM= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU= -github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= -github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/oras-project/oras-go v0.1.0 h1:mWWO1nAdcHQSC/zTUkckgDUOeGu1Tnxwyph1C8zKqsE= -github.com/oras-project/oras-go v0.1.0/go.mod h1:uts4oKaEWR4D+pajh79zPDGFJrx2aAtcVqC2jNb/8vM= -github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= -github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= -github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= -github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pivotal/image-relocation v0.0.0-20191111101224-e94aff6df06c/go.mod h1:/JNbQwGylYm3AQh8q+MBF8e/h0W1Jy20JGTvozuXYTE= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -1117,7 +853,6 @@ github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV 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/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -1131,7 +866,6 @@ github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod 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.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= 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= @@ -1139,7 +873,6 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1: github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/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.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= 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= @@ -1165,8 +898,6 @@ github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= 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/prometheus/tsdb v0.10.0 h1:If5rVCMTp6W2SiRAQFlbpJNgVlgMEd+U2GZckwK38ic= -github.com/prometheus/tsdb v0.10.0/go.mod h1:oi49uRhEe9dPUTlS3JRZOwJuVi6tmh10QSgwXEyGCt4= github.com/qri-io/jsonpointer v0.1.0 h1:OcTtTmorodUCRc2CZhj/ZwOET8zVj6uo0ArEmzoThZI= github.com/qri-io/jsonpointer v0.1.0/go.mod h1:DnJPaYgiKu56EuDp8TU5wFLdZIcAnb/uH9v37ZaMV64= github.com/qri-io/jsonschema v0.1.1 h1:t//Doa/gvMqJ0bDhG7PGIKfaWGGxRVaffp+bcvBGGEk= @@ -1175,25 +906,14 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn 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-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= -github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w= -github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351 h1:HXr/qUllAWv9riaI4zh2eXWKmCSDqVS/XH1MRHLKRwk= -github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg= -github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= -github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b h1:jUK33OXuZP/l6babJtnLo1qsGvq6G9so9KMflGAm4YA= github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b/go.mod h1:8458kAagoME2+LN5//WxE71ysZ3B7r22fdgb7qVmXSY= -github.com/sanathkr/yaml v0.0.0-20170819201035-0056894fa522 h1:fOCp11H0yuyAt2wqlbJtbyPzSgaxHTv8uN1pMpkG1t8= -github.com/sanathkr/yaml v0.0.0-20170819201035-0056894fa522/go.mod h1:tQTYKOQgxoH3v6dEmdHiz4JG+nbxWwM5fgPQUpSZqVQ= 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= @@ -1201,8 +921,6 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 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= @@ -1215,13 +933,9 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic 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/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8= -github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= 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/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= @@ -1235,7 +949,6 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= -github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= @@ -1254,13 +967,9 @@ github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5q github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= -github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= 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/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/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -1280,7 +989,6 @@ github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= github.com/theupdateframework/notary v0.6.1 h1:7wshjstgS9x9F5LuB1L5mBI2xNMObWqjz+cjWoom6l0= github.com/theupdateframework/notary v0.6.1/go.mod h1:MOfgIfmox8s7/7fduvB2xyPPMJCrjRLRizA8OFwpnKY= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 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/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85 h1:014iQD8i8EabPWK2XgUuOTxg5s2nhfDmq6GupskfUO8= @@ -1299,12 +1007,8 @@ github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ 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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= 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= @@ -1326,29 +1030,22 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= 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/xeipuuv/gojsonschema v0.0.0-20181112162635-ac52e6811b56/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= -github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= -github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= 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.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= 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 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= 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 h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.4.0/go.mod h1:nHzOclRkoj++EU9ZjSrZvRG0BXIWt8c7loYc0qXAFGQ= github.com/zclconf/go-cty v1.7.1/go.mod h1:VDR4+I79ubFBGm1uJac1226K5yANQFHeauxPBoP54+o= -github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is= @@ -1359,17 +1056,12 @@ github.com/zmap/zlint v0.0.0-20190806154020-fd021b4cfbeb/go.mod h1:29UiAJNsiVdvT 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.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= 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= @@ -1378,18 +1070,12 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= -go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= 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/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= 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= @@ -1398,21 +1084,17 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/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-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/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-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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= @@ -1453,7 +1135,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -1462,7 +1143,6 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180811021610-c39426892332/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-20181005035420-146acd28ed58/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= @@ -1472,7 +1152,6 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/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-20190320064053-1272bf9dcd53/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= @@ -1487,7 +1166,6 @@ golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73r 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-20191112182307-2180aed22343/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= @@ -1498,7 +1176,6 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -1555,14 +1232,12 @@ golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/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-20190321052220-f7bb7a8bee54/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-20190502175342-a43fa875dd82/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-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1570,26 +1245,21 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w 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-20190626221950-04f50cda93cb/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-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190830141801-acfa387b8d69/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-20191002063906-3421d5a6bb1c/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-20191112214154-59a1497f0cea/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-20191220142924-d4481acd189f/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= @@ -1607,7 +1277,6 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/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= @@ -1667,7 +1336,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/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-20190125232054-d66bd3c5d5a6/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= @@ -1678,17 +1346,13 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn 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-20190617190820-da514acc4774/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-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/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-20191014205221-18e3458ac98b/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/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= @@ -1697,7 +1361,6 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn 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-20200103221440-774c71fcf114/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= @@ -1710,7 +1373,6 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -1757,7 +1419,6 @@ google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBz google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 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= @@ -1774,7 +1435,6 @@ google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/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-20190530194941-fb225487d101/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= @@ -1817,7 +1477,6 @@ google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxH google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= 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= @@ -1870,12 +1529,9 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fatih/pool.v2 v2.0.0 h1:xIFeWtxifuQJGk/IEPKsTduEKcKvPmhoiVDGpC40nKg= gopkg.in/fatih/pool.v2 v2.0.0/go.mod h1:8xVGeu1/2jr2wm5V9SPuMht2H5AEmf5aFMGSQixtjTY= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/gorethink/gorethink.v3 v3.0.5 h1:e2Uc/Xe+hpcVQFsj6MuHlYog3r0JYpnTzwDj/y2O4MU= gopkg.in/gorethink/gorethink.v3 v3.0.5/go.mod h1:+3yIIHJUGMBK+wyPH+iN5TP+88ikFDfZdqTlK3Y9q8I= -gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw= -gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= @@ -1887,16 +1543,13 @@ 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 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 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.7/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= @@ -1909,8 +1562,6 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81 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= -helm.sh/helm/v3 v3.6.1-0.20210722154129-5946457ce9ea h1:KDEGuai0v16xSO6Uh1DdpX5ws03r0/aS1VQxIF7cvfI= -helm.sh/helm/v3 v3.6.1-0.20210722154129-5946457ce9ea/go.mod h1:dhTe384gzV1a7PMMBBF5M834+aY/ui4mar2udF0Kabs= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 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= @@ -1926,8 +1577,6 @@ k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= k8s.io/api v0.21.0 h1:gu5iGF4V6tfVCQ/R+8Hc0h7H1JuEhzyEi9S4R5LM8+Y= k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU= -k8s.io/apiextensions-apiserver v0.21.0 h1:Nd4uBuweg6ImzbxkC1W7xUNZcCV/8Vt10iTdTIVF3hw= -k8s.io/apiextensions-apiserver v0.21.0/go.mod h1:gsQGNtGkc/YoDG9loKI0V+oLZM4ljRPjc/sql5tmvzc= k8s.io/apimachinery v0.0.0-20180904193909-def12e63c512/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= k8s.io/apimachinery v0.0.0-20190806215851-162a2dabc72f/go.mod h1:+ntn62igV2hyNj7/0brOvXSMONE2KxcePkSxK7/9FFQ= k8s.io/apimachinery v0.0.0-20191004115801-a2eda9f80ab8/go.mod h1:llRdnznGEAqC3DcNm6yEj472xaFVfLM7hnYofMb12tQ= @@ -1939,10 +1588,6 @@ k8s.io/apimachinery v0.21.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswP 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/apiserver v0.21.0 h1:1hWMfsz+cXxB77k6/y0XxWxwl6l9OF26PC9QneUVn1Q= -k8s.io/apiserver v0.21.0/go.mod h1:w2YSn4/WIwYuxG5zJmcqtRdtqgW/J2JRgFAqps3bBpg= -k8s.io/cli-runtime v0.21.0 h1:/V2Kkxtf6x5NI2z+Sd/mIrq4FQyQ8jzZAUD6N5RnN7Y= -k8s.io/cli-runtime v0.21.0/go.mod h1:XoaHP93mGPF37MkLbjGVYqg3S1MnsFdKtiA/RZzzxOo= k8s.io/client-go v0.0.0-20180910083459-2cefa64ff137/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= k8s.io/client-go v0.0.0-20191016111102-bec269661e48/go.mod h1:hrwktSwYGI4JK+TJA3dMaFyyvHVi/aLarVHpbs8bgCU= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= @@ -1950,27 +1595,21 @@ 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/client-go v0.21.0 h1:n0zzzJsAQmJngpC0IhgFcApZyoGXPrDIAD601HD09ag= k8s.io/client-go v0.21.0/go.mod h1:nNBytTF9qPFDEhoqgEPaarobC8QPae13bElIVHzIglA= -k8s.io/code-generator v0.21.0/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q= 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/component-base v0.21.0 h1:tLLGp4BBjQaCpS/KiuWh7m2xqvAdsxLm4ATxHSe5Zpg= -k8s.io/component-base v0.21.0/go.mod h1:qvtjz6X0USWXbgmbfXR+Agik4RZ3jv2Bgr5QnZzdPYw= -k8s.io/component-helpers v0.21.0/go.mod h1:tezqefP7lxfvJyR+0a+6QtVrkZ/wIkyMLK4WcQ3Cj8U= 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-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.4.0 h1:lCJCxf/LIowc2IGS9TPjWDyXY4nOmdGdfcwwDQCOURQ= k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.8.0 h1:Q3gmuM9hKEjefWFFYF0Mat+YyFJvsUyYuwyNNJ5C9Ts= k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= @@ -1978,29 +1617,17 @@ k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4= k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= -k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 h1:vEx13qjvaZ4yfObSSXW7BrMc/KQBBT/Jyee8XtLf4x0= k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= -k8s.io/kubectl v0.21.0 h1:WZXlnG/yjcE4LWO2g6ULjFxtzK6H1TKzsfaBFuVIhNg= -k8s.io/kubectl v0.21.0/go.mod h1:EU37NukZRXn1TpAkMUoy8Z/B2u6wjHDS4aInsDzVvks= k8s.io/kubernetes v1.11.10/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/metrics v0.21.0/go.mod h1:L3Ji9EGPP1YBbfm9sPfEXSpnj8i24bfQbAFAsW0NueQ= k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20201110183641-67b214c5f920 h1:CbnUZsM497iRC5QMVkHwyl8s2tB3g7yaSHkYPkpgelw= 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/letsencrypt v0.0.3 h1:H7xDfhkaFFSYEJlKeq38RwX2jYcnTeHuDQyT+mMNMwM= -rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY= 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/kustomize/api v0.8.5 h1:bfCXGXDAbFbb/Jv5AhMj2BB8a5VAJuuQ5/KU69WtDjQ= -sigs.k8s.io/kustomize/api v0.8.5/go.mod h1:M377apnKT5ZHJS++6H4rQoCHmWtt6qTpp3mbe7p6OLY= -sigs.k8s.io/kustomize/cmd/config v0.9.7/go.mod h1:MvXCpHs77cfyxRmCNUQjIqCmZyYsbn5PyQpWiq44nW0= -sigs.k8s.io/kustomize/kustomize/v4 v4.0.5/go.mod h1:C7rYla7sI8EnxHE/xEhRBSHMNfcL91fx0uKmUlUhrBk= -sigs.k8s.io/kustomize/kyaml v0.10.15 h1:dSLgG78KyaxN4HylPXdK+7zB3k7sW6q3IcCmcfKA+aI= -sigs.k8s.io/kustomize/kyaml v0.10.15/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= @@ -2010,5 +1637,4 @@ sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= diff --git a/import-restrictions.yaml b/import-restrictions.yaml deleted file mode 100644 index bfbdf32e6..000000000 --- a/import-restrictions.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# Backends shouldn't depend on other backends or the cli -- path: ./aci - forbiddenImports: - - github.com/docker/compose-cli/cli - - github.com/docker/compose-cli/ecs - - github.com/docker/compose-cli/local -- path: ./ecs - forbiddenImports: - - github.com/docker/compose-cli/aci - - github.com/docker/compose-cli/cli - - github.com/docker/compose-cli/local -- path: ./local - forbiddenImports: - - github.com/docker/compose-cli/aci - - github.com/docker/compose-cli/cli - - github.com/docker/compose-cli/ecs diff --git a/kube/backend.go b/kube/backend.go deleted file mode 100644 index a439c2a3a..000000000 --- a/kube/backend.go +++ /dev/null @@ -1,70 +0,0 @@ -// +build kube - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package kube - -import ( - "github.com/docker/compose-cli/api/backend" - "github.com/docker/compose-cli/api/cloud" - "github.com/docker/compose-cli/api/containers" - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/api/resources" - "github.com/docker/compose-cli/api/secrets" - "github.com/docker/compose-cli/api/volumes" - "github.com/docker/compose-cli/pkg/api" -) - -const backendType = store.KubeContextType - -type kubeAPIService struct { - composeService api.Service -} - -func init() { - backend.Register(backendType, backendType, service, cloud.NotImplementedCloudService) -} - -func service() (backend.Service, error) { - s, err := NewComposeService() - if err != nil { - return nil, err - } - return &kubeAPIService{ - composeService: s, - }, nil -} - -func (s *kubeAPIService) ContainerService() containers.Service { - return nil -} - -func (s *kubeAPIService) ComposeService() api.Service { - return s.composeService -} - -func (s *kubeAPIService) SecretsService() secrets.Service { - return nil -} - -func (s *kubeAPIService) VolumeService() volumes.Service { - return nil -} - -func (s *kubeAPIService) ResourceService() resources.Service { - return nil -} diff --git a/kube/client/client.go b/kube/client/client.go deleted file mode 100644 index e363163f0..000000000 --- a/kube/client/client.go +++ /dev/null @@ -1,318 +0,0 @@ -// +build kube - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package client - -import ( - "context" - "fmt" - "io" - "net/http" - "os" - "strings" - "time" - - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/utils" - - "golang.org/x/sync/errgroup" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/portforward" - "k8s.io/client-go/tools/remotecommand" - "k8s.io/client-go/transport/spdy" -) - -// KubeClient API to access kube objects -type KubeClient struct { - client *kubernetes.Clientset - namespace string - config *rest.Config - ioStreams genericclioptions.IOStreams -} - -// NewKubeClient new kubernetes client -func NewKubeClient(config genericclioptions.RESTClientGetter) (*KubeClient, error) { - restConfig, err := config.ToRESTConfig() - if err != nil { - return nil, err - } - - clientset, err := kubernetes.NewForConfig(restConfig) - if err != nil { - return nil, fmt.Errorf("failed creating clientset. Error: %+v", err) - } - - namespace, _, err := config.ToRawKubeConfigLoader().Namespace() - if err != nil { - return nil, err - } - - return &KubeClient{ - client: clientset, - namespace: namespace, - config: restConfig, - ioStreams: genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}, - }, nil -} - -// GetPod retrieves a service pod -func (kc KubeClient) GetPod(ctx context.Context, projectName, serviceName string) (*corev1.Pod, error) { - pods, err := kc.client.CoreV1().Pods(kc.namespace).List(ctx, metav1.ListOptions{ - LabelSelector: fmt.Sprintf("%s=%s", api.ProjectLabel, projectName), - }) - if err != nil { - return nil, err - } - if pods == nil { - return nil, nil - } - var pod corev1.Pod - for _, p := range pods.Items { - service := p.Labels[api.ServiceLabel] - if service == serviceName { - pod = p - break - } - } - return &pod, nil -} - -// Exec executes a command in a container -func (kc KubeClient) Exec(ctx context.Context, projectName string, opts api.RunOptions) error { - pod, err := kc.GetPod(ctx, projectName, opts.Service) - if err != nil || pod == nil { - return err - } - if len(pod.Spec.Containers) == 0 { - return fmt.Errorf("no containers running in pod %s", pod.Name) - } - // get first container in the pod - container := &pod.Spec.Containers[0] - containerName := container.Name - - req := kc.client.CoreV1().RESTClient().Post(). - Resource("pods"). - Name(pod.Name). - Namespace(kc.namespace). - SubResource("exec") - - option := &corev1.PodExecOptions{ - Container: containerName, - Command: opts.Command, - Stdin: true, - Stdout: true, - Stderr: true, - TTY: opts.Tty, - } - - if opts.Stdin == nil { - option.Stdin = false - } - - scheme := runtime.NewScheme() - if err := corev1.AddToScheme(scheme); err != nil { - return fmt.Errorf("error adding to scheme: %v", err) - } - parameterCodec := runtime.NewParameterCodec(scheme) - req.VersionedParams(option, parameterCodec) - - exec, err := remotecommand.NewSPDYExecutor(kc.config, "POST", req.URL()) - if err != nil { - return err - } - return exec.Stream(remotecommand.StreamOptions{ - Stdin: opts.Stdin, - Stdout: opts.Stdout, - Stderr: opts.Stdout, - Tty: opts.Tty, - }) -} - -// GetContainers get containers for a given compose project -func (kc KubeClient) GetContainers(ctx context.Context, projectName string, all bool) ([]api.ContainerSummary, error) { - fieldSelector := "" - if !all { - fieldSelector = "status.phase=Running" - } - - pods, err := kc.client.CoreV1().Pods(kc.namespace).List(ctx, metav1.ListOptions{ - LabelSelector: fmt.Sprintf("%s=%s", api.ProjectLabel, projectName), - FieldSelector: fieldSelector, - }) - if err != nil { - return nil, err - } - services := map[string][]api.PortPublisher{} - result := []api.ContainerSummary{} - for _, pod := range pods.Items { - summary := podToContainerSummary(pod) - serviceName := pod.GetObjectMeta().GetLabels()[api.ServiceLabel] - ports, ok := services[serviceName] - if !ok { - s, err := kc.client.CoreV1().Services(kc.namespace).Get(ctx, serviceName, metav1.GetOptions{}) - if err != nil { - if !strings.Contains(err.Error(), "not found") { - return nil, err - } - result = append(result, summary) - continue - } - ports = []api.PortPublisher{} - if s != nil { - if s.Spec.Type == corev1.ServiceTypeLoadBalancer { - if len(s.Status.LoadBalancer.Ingress) > 0 { - port := api.PortPublisher{URL: s.Status.LoadBalancer.Ingress[0].IP} - if len(s.Spec.Ports) > 0 { - port.URL = fmt.Sprintf("%s:%d", port.URL, s.Spec.Ports[0].Port) - port.TargetPort = s.Spec.Ports[0].TargetPort.IntValue() - port.Protocol = string(s.Spec.Ports[0].Protocol) - } - ports = append(ports, port) - } - } - } - services[serviceName] = ports - } - summary.Publishers = ports - result = append(result, summary) - } - - return result, nil -} - -// GetLogs retrieves pod logs -func (kc *KubeClient) GetLogs(ctx context.Context, projectName string, consumer api.LogConsumer, follow bool) error { - pods, err := kc.client.CoreV1().Pods(kc.namespace).List(ctx, metav1.ListOptions{ - LabelSelector: fmt.Sprintf("%s=%s", api.ProjectLabel, projectName), - }) - if err != nil { - return err - } - eg, ctx := errgroup.WithContext(ctx) - for _, pod := range pods.Items { - podName := pod.Name - request := kc.client.CoreV1().Pods(kc.namespace).GetLogs(podName, &corev1.PodLogOptions{Follow: follow}) - service := pod.Labels[api.ServiceLabel] - w := utils.GetWriter(func(line string) { - consumer.Log(podName, service, line) - }) - - eg.Go(func() error { - r, err := request.Stream(ctx) - if err != nil { - return err - } - - defer r.Close() // nolint errcheck - _, err = io.Copy(w, r) - return err - }) - } - return eg.Wait() -} - -// WaitForPodState blocks until pods reach desired state -func (kc KubeClient) WaitForPodState(ctx context.Context, opts WaitForStatusOptions) error { - var timeout = time.Minute - if opts.Timeout != nil { - timeout = *opts.Timeout - } - - errch := make(chan error, 1) - done := make(chan bool) - go func() { - for { - time.Sleep(500 * time.Millisecond) - - pods, err := kc.client.CoreV1().Pods(kc.namespace).List(ctx, metav1.ListOptions{ - LabelSelector: fmt.Sprintf("%s=%s", api.ProjectLabel, opts.ProjectName), - }) - if err != nil { - errch <- err - } - stateReached, servicePods, err := checkPodsState(opts.Services, pods.Items, opts.Status) - if err != nil { - errch <- err - } - if opts.Log != nil { - for p, m := range servicePods { - opts.Log(p, stateReached, m) - } - } - - if stateReached { - done <- true - } - } - }() - - select { - case <-time.After(timeout): - return fmt.Errorf("timeout: pods did not reach expected state") - case err := <-errch: - if err != nil { - return err - } - case <-done: - return nil - } - return nil -} - -//MapPortsToLocalhost runs a port-forwarder daemon process -func (kc KubeClient) MapPortsToLocalhost(ctx context.Context, opts PortMappingOptions) error { - stopChannel := make(chan struct{}, 1) - readyChannel := make(chan struct{}) - - eg, ctx := errgroup.WithContext(ctx) - for serviceName, servicePorts := range opts.Services { - serviceName, servicePorts := serviceName, servicePorts - pod, err := kc.GetPod(ctx, opts.ProjectName, serviceName) - if err != nil { - return err - } - eg.Go(func() error { - ports := []string{} - for _, p := range servicePorts { - ports = append(ports, fmt.Sprintf("%d:%d", p.PublishedPort, p.TargetPort)) - } - - req := kc.client.CoreV1().RESTClient().Post(). - Resource("pods"). - Name(pod.Name). - Namespace(kc.namespace). - SubResource("portforward") - transport, upgrader, err := spdy.RoundTripperFor(kc.config) - if err != nil { - return err - } - dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", req.URL()) - fw, err := portforward.New(dialer, ports, stopChannel, readyChannel, os.Stdout, os.Stderr) - if err != nil { - return err - } - return fw.ForwardPorts() - }) - } - return eg.Wait() -} diff --git a/kube/client/client_test.go b/kube/client/client_test.go deleted file mode 100644 index 0c91295c7..000000000 --- a/kube/client/client_test.go +++ /dev/null @@ -1,56 +0,0 @@ -// +build kube - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package client - -import ( - "testing" - - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "gotest.tools/v3/assert" - - "github.com/docker/compose-cli/pkg/api" -) - -func TestPodToContainerSummary(t *testing.T) { - pod := v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "c1-123", - Labels: map[string]string{ - api.ProjectLabel: "myproject", - api.ServiceLabel: "service1", - }, - }, - Status: v1.PodStatus{ - Phase: "Running", - }, - } - - container := podToContainerSummary(pod) - - expected := api.ContainerSummary{ - ID: "c1-123", - Name: "c1-123", - Project: "myproject", - Service: "service1", - State: "Running", - } - assert.DeepEqual(t, container, expected) -} diff --git a/kube/client/utils.go b/kube/client/utils.go deleted file mode 100644 index ae4c08661..000000000 --- a/kube/client/utils.go +++ /dev/null @@ -1,110 +0,0 @@ -// +build kube - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package client - -import ( - "fmt" - "time" - - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/utils" - - corev1 "k8s.io/api/core/v1" -) - -func podToContainerSummary(pod corev1.Pod) api.ContainerSummary { - state := api.RUNNING - - if pod.DeletionTimestamp != nil { - state = api.REMOVING - } else { - for _, container := range pod.Status.ContainerStatuses { - if container.State.Waiting != nil || container.State.Terminated != nil { - state = api.UPDATING - break - } - } - if state == api.RUNNING && pod.Status.Phase != corev1.PodRunning { - state = string(pod.Status.Phase) - } - } - - return api.ContainerSummary{ - ID: pod.GetObjectMeta().GetName(), - Name: pod.GetObjectMeta().GetName(), - Service: pod.GetObjectMeta().GetLabels()[api.ServiceLabel], - State: state, - Project: pod.GetObjectMeta().GetLabels()[api.ProjectLabel], - } -} - -func checkPodsState(services []string, pods []corev1.Pod, status string) (bool, map[string]string, error) { - servicePods := map[string]string{} - stateReached := true - for _, pod := range pods { - service := pod.Labels[api.ServiceLabel] - - if len(services) > 0 && !utils.StringContains(services, service) { - continue - } - containersRunning := true - for _, container := range pod.Status.ContainerStatuses { - if container.State.Running == nil { - containersRunning = false - break - } - } - servicePods[service] = pod.Status.Message - - if status == api.REMOVING { - continue - } - if pod.Status.Phase == corev1.PodFailed { - return false, servicePods, fmt.Errorf(pod.Status.Reason) - } - if status == api.RUNNING && (pod.Status.Phase != corev1.PodRunning || !containersRunning) { - stateReached = false - } - } - if status == api.REMOVING && len(servicePods) > 0 { - stateReached = false - } - return stateReached, servicePods, nil -} - -// LogFunc defines a custom logger function (progress writer events) -type LogFunc func(pod string, stateReached bool, message string) - -// WaitForStatusOptions hold the state pods should reach -type WaitForStatusOptions struct { - ProjectName string - Services []string - Status string - Timeout *time.Duration - Log LogFunc -} - -// Ports holds published ports data -type Ports []api.PortPublisher - -// PortMappingOptions holds the port mapping for project services -type PortMappingOptions struct { - ProjectName string - Services map[string]Ports -} diff --git a/kube/compose.go b/kube/compose.go deleted file mode 100644 index f33663a13..000000000 --- a/kube/compose.go +++ /dev/null @@ -1,315 +0,0 @@ -// +build kube - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package kube - -import ( - "context" - "fmt" - "strings" - - "github.com/compose-spec/compose-go/types" - "github.com/pkg/errors" - - apicontext "github.com/docker/compose-cli/api/context" - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/kube/client" - "github.com/docker/compose-cli/kube/helm" - "github.com/docker/compose-cli/kube/resources" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/progress" - utils2 "github.com/docker/compose-cli/pkg/utils" - "github.com/docker/compose-cli/utils" -) - -type composeService struct { - sdk *helm.Actions - client *client.KubeClient -} - -// NewComposeService create a kubernetes implementation of the api.Service API -func NewComposeService() (api.Service, error) { - contextStore := store.Instance() - currentContext := apicontext.Current() - var kubeContext store.KubeContext - - if err := contextStore.GetEndpoint(currentContext, &kubeContext); err != nil { - return nil, err - } - config, err := resources.LoadConfig(kubeContext) - if err != nil { - return nil, err - } - actions, err := helm.NewActions(config) - if err != nil { - return nil, err - } - apiClient, err := client.NewKubeClient(config) - if err != nil { - return nil, err - } - - return &composeService{ - sdk: actions, - client: apiClient, - }, nil -} - -// Up executes the equivalent to a `compose up` -func (s *composeService) Up(ctx context.Context, project *types.Project, options api.UpOptions) error { - return progress.Run(ctx, func(ctx context.Context) error { - return s.up(ctx, project) - }) -} - -func (s *composeService) up(ctx context.Context, project *types.Project) error { - w := progress.ContextWriter(ctx) - - eventName := "Convert Compose file to Helm charts" - w.Event(progress.CreatingEvent(eventName)) - - chart, err := helm.GetChartInMemory(project) - if err != nil { - return err - } - w.Event(progress.NewEvent(eventName, progress.Done, "")) - - stack, err := s.sdk.Get(project.Name) - if err != nil || stack == nil { - // install stack - eventName = "Install Compose stack" - w.Event(progress.CreatingEvent(eventName)) - - err = s.sdk.InstallChart(project.Name, chart, func(format string, v ...interface{}) { - message := fmt.Sprintf(format, v...) - w.Event(progress.NewEvent(eventName, progress.Done, message)) - }) - - } else { - // update stack - eventName = "Updating Compose stack" - w.Event(progress.CreatingEvent(eventName)) - - err = s.sdk.UpdateChart(project.Name, chart, func(format string, v ...interface{}) { - message := fmt.Sprintf(format, v...) - w.Event(progress.NewEvent(eventName, progress.Done, message)) - }) - } - if err != nil { - return err - } - - w.Event(progress.NewEvent(eventName, progress.Done, "")) - - return s.client.WaitForPodState(ctx, client.WaitForStatusOptions{ - ProjectName: project.Name, - Services: project.ServiceNames(), - Status: api.RUNNING, - Log: func(pod string, stateReached bool, message string) { - state := progress.Done - if !stateReached { - state = progress.Working - } - w.Event(progress.NewEvent(pod, state, message)) - }, - }) -} - -// Down executes the equivalent to a `compose down` -func (s *composeService) Down(ctx context.Context, projectName string, options api.DownOptions) error { - if options.Volumes { - return errors.Wrap(api.ErrNotImplemented, "--volumes option is not supported on Kubernetes") - } - if options.Images != "" { - return errors.Wrap(api.ErrNotImplemented, "--rmi option is not supported on Kubernetes") - } - return progress.Run(ctx, func(ctx context.Context) error { - return s.down(ctx, projectName, options) - }) -} - -func (s *composeService) down(ctx context.Context, projectName string, options api.DownOptions) error { - w := progress.ContextWriter(ctx) - eventName := fmt.Sprintf("Remove %s", projectName) - w.Event(progress.CreatingEvent(eventName)) - - logger := func(format string, v ...interface{}) { - message := fmt.Sprintf(format, v...) - if strings.Contains(message, "Starting delete") { - action := strings.Replace(message, "Starting delete for", "Delete", 1) - - w.Event(progress.CreatingEvent(action)) - w.Event(progress.NewEvent(action, progress.Done, "")) - return - } - w.Event(progress.NewEvent(eventName, progress.Working, message)) - } - err := s.sdk.Uninstall(projectName, logger) - if err != nil { - return err - } - - events := []string{} - err = s.client.WaitForPodState(ctx, client.WaitForStatusOptions{ - ProjectName: projectName, - Services: nil, - Status: api.REMOVING, - Timeout: options.Timeout, - Log: func(pod string, stateReached bool, message string) { - state := progress.Done - if !stateReached { - state = progress.Working - } - w.Event(progress.NewEvent(pod, state, message)) - if !utils2.StringContains(events, pod) { - events = append(events, pod) - } - }, - }) - if err != nil { - return err - } - for _, e := range events { - w.Event(progress.NewEvent(e, progress.Done, "")) - } - w.Event(progress.NewEvent(eventName, progress.Done, "")) - return nil -} - -// List executes the equivalent to a `docker stack ls` -func (s *composeService) List(ctx context.Context, opts api.ListOptions) ([]api.Stack, error) { - return s.sdk.ListReleases() -} - -// Build executes the equivalent to a `compose build` -func (s *composeService) Build(ctx context.Context, project *types.Project, options api.BuildOptions) error { - return api.ErrNotImplemented -} - -// Push executes the equivalent ot a `compose push` -func (s *composeService) Push(ctx context.Context, project *types.Project, options api.PushOptions) error { - return api.ErrNotImplemented -} - -// Pull executes the equivalent of a `compose pull` -func (s *composeService) Pull(ctx context.Context, project *types.Project, options api.PullOptions) error { - return api.ErrNotImplemented -} - -// Create executes the equivalent to a `compose create` -func (s *composeService) Create(ctx context.Context, project *types.Project, opts api.CreateOptions) error { - return api.ErrNotImplemented -} - -// Start executes the equivalent to a `compose start` -func (s *composeService) Start(ctx context.Context, project *types.Project, options api.StartOptions) error { - return api.ErrNotImplemented -} - -// Restart executes the equivalent to a `compose restart` -func (s *composeService) Restart(ctx context.Context, project *types.Project, options api.RestartOptions) error { - return api.ErrNotImplemented -} - -// Stop executes the equivalent to a `compose stop` -func (s *composeService) Stop(ctx context.Context, project *types.Project, options api.StopOptions) error { - return api.ErrNotImplemented -} - -// Copy copies a file/folder between a service container and the local filesystem -func (s *composeService) Copy(ctx context.Context, project *types.Project, options api.CopyOptions) error { - return api.ErrNotImplemented -} - -// Logs executes the equivalent to a `compose logs` -func (s *composeService) Logs(ctx context.Context, projectName string, consumer api.LogConsumer, options api.LogOptions) error { - if len(options.Services) > 0 { - consumer = utils.FilteredLogConsumer(consumer, options.Services) - } - return s.client.GetLogs(ctx, projectName, consumer, options.Follow) -} - -// Ps executes the equivalent to a `compose ps` -func (s *composeService) Ps(ctx context.Context, projectName string, options api.PsOptions) ([]api.ContainerSummary, error) { - return s.client.GetContainers(ctx, projectName, options.All) -} - -// Convert translate compose model into backend's native format -func (s *composeService) Convert(ctx context.Context, project *types.Project, options api.ConvertOptions) ([]byte, error) { - - chart, err := helm.GetChartInMemory(project) - if err != nil { - return nil, err - } - - if options.Output != "" { - _, err := helm.SaveChart(chart, options.Output) - return nil, err - } - - buff := []byte{} - for _, f := range chart.Raw { - header := "\n" + f.Name + "\n" + strings.Repeat("-", len(f.Name)) + "\n" - buff = append(buff, []byte(header)...) - buff = append(buff, f.Data...) - buff = append(buff, []byte("\n")...) - } - return buff, nil -} - -func (s *composeService) Kill(ctx context.Context, project *types.Project, options api.KillOptions) error { - return api.ErrNotImplemented -} - -// RunOneOffContainer creates a service oneoff container and starts its dependencies -func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) { - return 0, api.ErrNotImplemented -} - -func (s *composeService) Remove(ctx context.Context, project *types.Project, options api.RemoveOptions) error { - return api.ErrNotImplemented -} - -// Exec executes a command in a running service container -func (s *composeService) Exec(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) { - return 0, s.client.Exec(ctx, project.Name, opts) -} - -func (s *composeService) Pause(ctx context.Context, project string, options api.PauseOptions) error { - return api.ErrNotImplemented -} - -func (s *composeService) UnPause(ctx context.Context, project string, options api.PauseOptions) error { - return api.ErrNotImplemented -} - -func (s *composeService) Top(ctx context.Context, projectName string, services []string) ([]api.ContainerProcSummary, error) { - return nil, api.ErrNotImplemented -} - -func (s *composeService) Events(ctx context.Context, project string, options api.EventsOptions) error { - return api.ErrNotImplemented -} - -func (s *composeService) Port(ctx context.Context, project string, service string, port int, options api.PortOptions) (string, int, error) { - return "", 0, api.ErrNotImplemented -} - -func (s *composeService) Images(ctx context.Context, projectName string, options api.ImagesOptions) ([]api.ImageSummary, error) { - return nil, api.ErrNotImplemented -} diff --git a/kube/context.go b/kube/context.go deleted file mode 100644 index 0159036a7..000000000 --- a/kube/context.go +++ /dev/null @@ -1,125 +0,0 @@ -// +build kube - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package kube - -import ( - "fmt" - - "github.com/AlecAivazis/survey/v2/terminal" - - "github.com/docker/compose-cli/api/context/store" - "github.com/docker/compose-cli/kube/resources" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/prompt" -) - -// ContextParams options for creating a Kubernetes context -type ContextParams struct { - KubeContextName string - Description string - KubeConfigPath string - FromEnvironment bool -} - -// CreateContextData create Docker context data -func (cp ContextParams) CreateContextData() (interface{}, string, error) { - if cp.FromEnvironment { - // we use the current kubectl context from a $KUBECONFIG path - return store.KubeContext{ - FromEnvironment: cp.FromEnvironment, - }, cp.getDescription(), nil - } - user := prompt.User{} - selectContext := func() error { - contexts, err := resources.ListAvailableKubeConfigContexts(cp.KubeConfigPath) - if err != nil { - return err - } - - selected, err := user.Select("Select kubeconfig context", contexts) - if err != nil { - if err == terminal.InterruptErr { - return api.ErrCanceled - } - return err - } - cp.KubeContextName = contexts[selected] - return nil - } - - if cp.KubeConfigPath != "" { - if cp.KubeContextName != "" { - return store.KubeContext{ - ContextName: cp.KubeContextName, - KubeconfigPath: cp.KubeConfigPath, - FromEnvironment: cp.FromEnvironment, - }, cp.getDescription(), nil - } - err := selectContext() - if err != nil { - return nil, "", err - } - } else { - - // interactive - var options []string - var actions []func() error - - options = append(options, "Context from kubeconfig file") - actions = append(actions, selectContext) - - options = append(options, "Kubernetes environment variables") - actions = append(actions, func() error { - cp.FromEnvironment = true - return nil - }) - - selected, err := user.Select("Create a Docker context using:", options) - if err != nil { - if err == terminal.InterruptErr { - return nil, "", api.ErrCanceled - } - return nil, "", err - } - - err = actions[selected]() - if err != nil { - return nil, "", err - } - } - return store.KubeContext{ - ContextName: cp.KubeContextName, - KubeconfigPath: cp.KubeConfigPath, - FromEnvironment: cp.FromEnvironment, - }, cp.getDescription(), nil -} - -func (cp ContextParams) getDescription() string { - if cp.Description != "" { - return cp.Description - } - if cp.FromEnvironment { - return "From environment variables" - } - configFile := "default kube config" - if cp.KubeConfigPath != "" { - configFile = cp.KubeConfigPath - } - return fmt.Sprintf("%s (in %s)", cp.KubeContextName, configFile) -} diff --git a/kube/context_test.go b/kube/context_test.go deleted file mode 100644 index 12913ce57..000000000 --- a/kube/context_test.go +++ /dev/null @@ -1,58 +0,0 @@ -// +build kube - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package kube - -import ( - "testing" - - "gotest.tools/v3/assert" -) - -func TestContextDescriptionIfEnvVar(t *testing.T) { - cp := ContextParams{ - FromEnvironment: true, - } - description := cp.getDescription() - assert.Equal(t, description, "From environment variables") -} - -func TestContextDescriptionIfProvided(t *testing.T) { - cp := ContextParams{ - Description: "custom description", - FromEnvironment: true, - } - description := cp.getDescription() - assert.Equal(t, description, "custom description") -} - -func TestContextDescriptionIfConfigFile(t *testing.T) { - cp := ContextParams{ - KubeContextName: "my-context", - KubeConfigPath: "~/.kube/config", - } - description := cp.getDescription() - assert.Equal(t, description, "my-context (in ~/.kube/config)") -} -func TestContextDescriptionIfDefaultConfigFile(t *testing.T) { - cp := ContextParams{ - KubeContextName: "my-context", - } - description := cp.getDescription() - assert.Equal(t, description, "my-context (in default kube config)") -} diff --git a/kube/e2e/compose_test.go b/kube/e2e/compose_test.go deleted file mode 100644 index bda2ec62c..000000000 --- a/kube/e2e/compose_test.go +++ /dev/null @@ -1,125 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package e2e - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "testing" - "time" - - testify "github.com/stretchr/testify/assert" - "gotest.tools/v3/assert" - "gotest.tools/v3/icmd" - - . "github.com/docker/compose-cli/utils/e2e" -) - -var binDir string - -func TestMain(m *testing.M) { - p, cleanup, err := SetupExistingCLI() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - binDir = p - exitCode := m.Run() - cleanup() - os.Exit(exitCode) -} - -func TestComposeUp(t *testing.T) { - c := NewParallelE2eCLI(t, binDir) - - const projectName = "compose-kube-demo" - kubeconfig := filepath.Join(c.ConfigDir, "kubeconfig") - kindClusterName := "e2e" - kubeContextName := "kind-" + kindClusterName - dockerContextName := "kube-e2e-ctx" - - t.Run("create kube cluster", func(t *testing.T) { - c.RunCmd("kind", "create", "cluster", "--name", kindClusterName, "--kubeconfig", kubeconfig, "--wait", "180s") - }) - defer func() { - c.RunDockerCmd("context", "use", "default") - c.RunCmd("kind", "delete", "cluster", "--name", kindClusterName, "--kubeconfig", kubeconfig) - }() - - t.Run("create kube context", func(t *testing.T) { - res := c.RunDockerCmd("context", "create", "kubernetes", "--kubeconfig", kubeconfig, "--kubecontext", kubeContextName, dockerContextName) - res.Assert(t, icmd.Expected{Out: fmt.Sprintf("Successfully created kube context %q", dockerContextName)}) - c.RunDockerCmd("context", "use", dockerContextName) - res = c.RunDockerCmd("context", "ls") - res.Assert(t, icmd.Expected{Out: fmt.Sprintf("%s * kube %s (in %s)", dockerContextName, kubeContextName, kubeconfig)}) - }) - - t.Run("up", func(t *testing.T) { - c.RunDockerCmd("compose", "-f", "./kube-simple-demo/demo_sentences.yaml", "--project-name", projectName, "up", "-d") - }) - - t.Run("compose ls", func(t *testing.T) { - res := c.RunDockerCmd("compose", "ls", "--format", "json") - res.Assert(t, icmd.Expected{Out: `[{"Name":"compose-kube-demo","Status":"deployed"}]`}) - }) - - t.Run("compose ps --all", func(t *testing.T) { - getServiceRegx := func(service string) string { - // match output with random hash / spaces like: - // db-698f4dd798-jd9gw db Running - return fmt.Sprintf("%s-.*\\s+%s\\s+Running\\s+", service, service) - } - res := c.RunDockerCmd("compose", "-p", projectName, "ps", "--all") - testify.Regexp(t, getServiceRegx("db"), res.Stdout()) - testify.Regexp(t, getServiceRegx("words"), res.Stdout()) - testify.Regexp(t, getServiceRegx("web"), res.Stdout()) - - assert.Equal(t, len(Lines(res.Stdout())), 4, res.Stdout()) - }) - - // to be revisited - /*t.Run("compose ps hides non running containers", func(t *testing.T) { - res := c.RunDockerCmd("compose", "-p", projectName, "ps") - assert.Equal(t, len(Lines(res.Stdout())), 1, res.Stdout()) - })*/ - - t.Run("check running project", func(t *testing.T) { - // Docker Desktop kube cluster automatically exposes ports on the host, this is not the case with kind on Desktop, - //we need to connect to the clusterIP, from the kind container - res := c.RunCmd("sh", "-c", "kubectl --kubeconfig "+kubeconfig+" get service/web -o json | jq -r '.spec.clusterIP'") - clusterIP := strings.ReplaceAll(strings.TrimSpace(res.Stdout()), `"`, "") - - endpoint := fmt.Sprintf("http://%s:80/words/noun", clusterIP) - c.WaitForCmdResult(icmd.Command("docker", "--context", "default", "exec", "e2e-control-plane", "curl", endpoint), StdoutContains(`"word":`), 3*time.Minute, 3*time.Second) - }) - - t.Run("compose logs web", func(t *testing.T) { - res := c.RunDockerCmd("compose", "--project-name", projectName, "logs", "web") - assert.Assert(t, strings.Contains(res.Stdout(), "Listening on port 80"), res.Stdout()) - }) - - t.Run("down", func(t *testing.T) { - _ = c.RunDockerCmd("compose", "--project-name", projectName, "down") - }) - - t.Run("check stack after down", func(t *testing.T) { - res := c.RunDockerCmd("compose", "ls") - assert.Assert(t, !strings.Contains(res.Combined(), projectName), res.Combined()) - }) -} diff --git a/kube/e2e/kube-simple-demo/demo_sentences.yaml b/kube/e2e/kube-simple-demo/demo_sentences.yaml deleted file mode 100644 index 85107558c..000000000 --- a/kube/e2e/kube-simple-demo/demo_sentences.yaml +++ /dev/null @@ -1,13 +0,0 @@ -services: - db: - build: aci-demo/db - image: gtardif/sentences-db - - words: - build: aci-demo/words - image: gtardif/sentences-api - web: - build: aci-demo/web - image: gtardif/sentences-web - ports: - - "80:80" diff --git a/kube/helm/chart.go b/kube/helm/chart.go deleted file mode 100644 index 52a4362dc..000000000 --- a/kube/helm/chart.go +++ /dev/null @@ -1,168 +0,0 @@ -// +build kube - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package helm - -import ( - "bytes" - "encoding/json" - "html/template" - "os" - "path/filepath" - "strings" - - "github.com/compose-spec/compose-go/types" - "github.com/docker/compose-cli/kube/resources" - "github.com/pkg/errors" - "gopkg.in/yaml.v3" - - chart "helm.sh/helm/v3/pkg/chart" - loader "helm.sh/helm/v3/pkg/chart/loader" - "k8s.io/apimachinery/pkg/runtime" -) - -//ConvertToChart convert Kube objects to helm chart -func ConvertToChart(name string, objects map[string]runtime.Object) (*chart.Chart, error) { - - files := []*loader.BufferedFile{ - { - Name: "README.md", - Data: []byte("This chart was created by converting a Compose file"), - }} - - chart := `name: {{.Name}} -description: A generated Helm Chart for {{.Name}} from Skippbox Kompose -version: 0.0.1 -apiVersion: v1 -keywords: - - {{.Name}} -sources: -home: -` - - t, err := template.New("ChartTmpl").Parse(chart) - if err != nil { - return nil, err - } - type ChartDetails struct { - Name string - } - var chartData bytes.Buffer - err = t.Execute(&chartData, ChartDetails{Name: name}) - if err != nil { - return nil, err - } - files = append(files, &loader.BufferedFile{ - Name: "Chart.yaml", - Data: chartData.Bytes(), - }) - - for name, o := range objects { - j, err := json.Marshal(o) - if err != nil { - return nil, err - } - buf, err := jsonToYaml(j, 2) - if err != nil { - return nil, err - } - files = append(files, &loader.BufferedFile{ - Name: filepath.Join("templates", name), - Data: buf, - }) - - } - return loader.LoadFiles(files) -} - -// Convert JSON to YAML. -func jsonToYaml(j []byte, spaces int) ([]byte, error) { - // Convert the JSON to an object. - var jsonObj interface{} - // We are using yaml.Unmarshal here (instead of json.Unmarshal) because the - // Go JSON library doesn't try to pick the right number type (int, float, - // etc.) when unmarshling to interface{}, it just picks float64 - // universally. go-yaml does go through the effort of picking the right - // number type, so we can preserve number type throughout this process. - err := yaml.Unmarshal(j, &jsonObj) - if err != nil { - return nil, err - } - - var b bytes.Buffer - encoder := yaml.NewEncoder(&b) - encoder.SetIndent(spaces) - if err := encoder.Encode(jsonObj); err != nil { - return nil, err - } - return b.Bytes(), nil -} - -// GetChartInMemory get memory representation of helm chart -func GetChartInMemory(project *types.Project) (*chart.Chart, error) { - // replace _ with - in volume names - for k, v := range project.Volumes { - volumeName := strings.ReplaceAll(k, "_", "-") - if volumeName != k { - project.Volumes[volumeName] = v - delete(project.Volumes, k) - } - } - objects, err := resources.MapToKubernetesObjects(project) - if err != nil { - return nil, err - } - //in memory files - return ConvertToChart(project.Name, objects) -} - -// SaveChart saves the chart to directory -func SaveChart(c *chart.Chart, dest string) (string, error) { - dir, err := filepath.Abs(dest) - if err != nil { - return "", err - } - for _, file := range c.Raw { - filename := filepath.Join(dir, file.Name) - filedir := filepath.Dir(filename) - - stat, err := os.Stat(filedir) - - if err != nil { - if os.IsNotExist(err) { - if err2 := os.MkdirAll(filedir, 0755); err2 != nil { - return "", err2 - } - } else { - return "", err - } - } else if !stat.IsDir() { - return "", errors.Errorf("%s: not a directory", dest) - } - - f, err := os.Create(filename) - if err != nil { - return "", err - } - _, err = f.Write(file.Data) - if err != nil { - return "", err - } - } - return dir, nil -} diff --git a/kube/helm/helm.go b/kube/helm/helm.go deleted file mode 100644 index 846102606..000000000 --- a/kube/helm/helm.go +++ /dev/null @@ -1,141 +0,0 @@ -// +build kube - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package helm - -import ( - "errors" - - "github.com/docker/compose-cli/pkg/api" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart" - env "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/release" - "k8s.io/cli-runtime/pkg/genericclioptions" -) - -// Actions implements charts installation management -type Actions struct { - Config *action.Configuration - Namespace string - initialize func(f func(format string, v ...interface{})) error -} - -const helmDriver = "configmap" - -// NewActions new helm action -func NewActions(getter genericclioptions.RESTClientGetter) (*Actions, error) { - if getter == nil { - settings := env.New() - getter = settings.RESTClientGetter() - } - - namespace := "default" - if ns, _, err := getter.ToRawKubeConfigLoader().Namespace(); err == nil { - namespace = ns - } - actions := &Actions{ - Config: &action.Configuration{ - RESTClientGetter: getter, - }, - Namespace: namespace, - } - - actions.initialize = func(f func(format string, v ...interface{})) error { - err := actions.Config.Init(getter, namespace, helmDriver, f) - if err != nil { - return err - } - return actions.Config.KubeClient.IsReachable() - } - err := actions.initialize(nil) // by default no logger, users might re-initialize with another logger function - if err != nil { - return nil, err - } - return actions, nil -} - -// InstallChart installs chart -func (hc *Actions) InstallChart(name string, chart *chart.Chart, logger func(format string, v ...interface{})) error { - err := hc.initialize(logger) - if err != nil { - return err - } - - actInstall := action.NewInstall(hc.Config) - actInstall.ReleaseName = name - actInstall.Namespace = hc.Namespace - _, err = actInstall.Run(chart, map[string]interface{}{}) - return err -} - -// UpdateChart upgrades chart -func (hc *Actions) UpdateChart(name string, chart *chart.Chart, logger func(format string, v ...interface{})) error { - err := hc.initialize(logger) - if err != nil { - return err - } - - actUpgrade := action.NewUpgrade(hc.Config) - actUpgrade.Namespace = hc.Namespace - _, err = actUpgrade.Run(name, chart, map[string]interface{}{}) - return err -} - -// Uninstall uninstall chart -func (hc *Actions) Uninstall(name string, logger func(format string, v ...interface{})) error { - err := hc.initialize(logger) - if err != nil { - return err - } - - release, err := hc.Get(name) - if err != nil { - return err - } - if release == nil { - return errors.New("no release found with the name provided") - } - actUninstall := action.NewUninstall(hc.Config) - _, err = actUninstall.Run(name) - return err -} - -// Get get released object for a named chart -func (hc *Actions) Get(name string) (*release.Release, error) { - actGet := action.NewGet(hc.Config) - return actGet.Run(name) -} - -// ListReleases lists chart releases -func (hc *Actions) ListReleases() ([]api.Stack, error) { - actList := action.NewList(hc.Config) - releases, err := actList.Run() - if err != nil { - return nil, err - } - result := []api.Stack{} - for _, rel := range releases { - result = append(result, api.Stack{ - ID: rel.Name, - Name: rel.Name, - Status: string(rel.Info.Status), - }) - } - return result, nil -} diff --git a/kube/resources/context.go b/kube/resources/context.go deleted file mode 100644 index 5a40393d6..000000000 --- a/kube/resources/context.go +++ /dev/null @@ -1,97 +0,0 @@ -// +build kube - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package resources - -import ( - "fmt" - "os" - - "github.com/docker/compose-cli/api/context/store" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/tools/clientcmd/api" -) - -// ListAvailableKubeConfigContexts list kube contexts -func ListAvailableKubeConfigContexts(kubeconfig string) ([]string, error) { - config, err := getKubeConfig(kubeconfig) - if err != nil { - return nil, err - } - contexts := []string{} - for k := range config.Contexts { - contexts = append(contexts, k) - } - return contexts, nil -} - -// LoadConfig returns kubeconfig data referenced in the docker context -func LoadConfig(ctx store.KubeContext) (*genericclioptions.ConfigFlags, error) { - if ctx.FromEnvironment { - return nil, nil - } - config, err := getKubeConfig(ctx.KubeconfigPath) - if err != nil { - return nil, err - } - contextName := ctx.ContextName - if contextName == "" { - contextName = config.CurrentContext - } - - context, ok := config.Contexts[contextName] - if !ok { - return nil, fmt.Errorf("context name %s not found in kubeconfig", contextName) - } - cluster, ok := config.Clusters[context.Cluster] - if !ok { - return nil, fmt.Errorf("cluster %s not found for context %s", context.Cluster, contextName) - } - // bind to kubernetes config flags - return &genericclioptions.ConfigFlags{ - Context: &ctx.ContextName, - KubeConfig: &ctx.KubeconfigPath, - - Namespace: &context.Namespace, - ClusterName: &context.Cluster, - - APIServer: &cluster.Server, - CAFile: &cluster.CertificateAuthority, - }, nil -} - -func getKubeConfig(kubeconfig string) (*api.Config, error) { - config, err := clientcmd.NewDefaultPathOptions().GetStartingConfig() - if err != nil { - return nil, err - } - if kubeconfig != "" { - f, err := os.Stat(kubeconfig) - if os.IsNotExist(err) { - return nil, err - } - if f.IsDir() { - return nil, fmt.Errorf("%s not a config file", kubeconfig) - } - - config = clientcmd.GetConfigFromFileOrDie(kubeconfig) - } - - return config, nil -} diff --git a/kube/resources/kube.go b/kube/resources/kube.go deleted file mode 100644 index 0b2913bdf..000000000 --- a/kube/resources/kube.go +++ /dev/null @@ -1,238 +0,0 @@ -// +build kube - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package resources - -import ( - "fmt" - "log" - "strings" - "time" - - "github.com/compose-spec/compose-go/types" - "github.com/docker/compose-cli/pkg/api" - apps "k8s.io/api/apps/v1" - core "k8s.io/api/core/v1" - resource "k8s.io/apimachinery/pkg/api/resource" - meta "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/intstr" -) - -const ( - clusterIPHeadless = "None" -) - -//MapToKubernetesObjects maps compose project to Kubernetes objects -func MapToKubernetesObjects(project *types.Project) (map[string]runtime.Object, error) { - objects := map[string]runtime.Object{} - - secrets, err := toSecretSpecs(project) - if err != nil { - return nil, err - } - if len(secrets) > 0 { - for _, secret := range secrets { - name := secret.Name[len(project.Name)+1:] - objects[fmt.Sprintf("%s-secret.yaml", name)] = &secret - } - } - - for _, service := range project.Services { - svcObject := mapToService(project, service) - if svcObject != nil { - objects[fmt.Sprintf("%s-service.yaml", service.Name)] = svcObject - } else { - log.Println("Missing port mapping from service config.") - } - - if service.Deploy != nil && service.Deploy.Mode == "global" { - daemonset, err := mapToDaemonset(project, service) - if err != nil { - return nil, err - } - objects[fmt.Sprintf("%s-daemonset.yaml", service.Name)] = daemonset - } else { - deployment, err := mapToDeployment(project, service) - if err != nil { - return nil, err - } - objects[fmt.Sprintf("%s-deployment.yaml", service.Name)] = deployment - } - for _, vol := range service.Volumes { - if vol.Type == "volume" { - vol.Source = strings.ReplaceAll(vol.Source, "_", "-") - objects[fmt.Sprintf("%s-persistentvolumeclaim.yaml", vol.Source)] = mapToPVC(project, service, vol) - } - } - } - return objects, nil -} - -func mapToService(project *types.Project, service types.ServiceConfig) *core.Service { - ports := []core.ServicePort{} - serviceType := core.ServiceTypeClusterIP - clusterIP := "" - for _, p := range service.Ports { - if p.Published != 0 { - serviceType = core.ServiceTypeLoadBalancer - } - protocol := toProtocol(p.Protocol) - ports = append(ports, - core.ServicePort{ - Name: fmt.Sprintf("%d-%s", p.Published, strings.ToLower(string(protocol))), - Port: int32(p.Published), - TargetPort: intstr.FromInt(int(p.Target)), - Protocol: protocol, - }) - } - if len(ports) == 0 { // headless service - clusterIP = clusterIPHeadless - } - return &core.Service{ - TypeMeta: meta.TypeMeta{ - Kind: "Service", - APIVersion: "v1", - }, - ObjectMeta: meta.ObjectMeta{ - Name: service.Name, - }, - Spec: core.ServiceSpec{ - ClusterIP: clusterIP, - Selector: selectorLabels(project.Name, service.Name), - Ports: ports, - Type: serviceType, - }, - } -} - -func mapToDeployment(project *types.Project, service types.ServiceConfig) (*apps.Deployment, error) { - labels := selectorLabels(project.Name, service.Name) - selector := new(meta.LabelSelector) - selector.MatchLabels = make(map[string]string) - for key, val := range labels { - selector.MatchLabels[key] = val - } - podTemplate, err := toPodTemplate(project, service, labels) - if err != nil { - return nil, err - } - return &apps.Deployment{ - TypeMeta: meta.TypeMeta{ - Kind: "Deployment", - APIVersion: "apps/v1", - }, - ObjectMeta: meta.ObjectMeta{ - Name: service.Name, - Labels: labels, - }, - Spec: apps.DeploymentSpec{ - Selector: selector, - Replicas: toReplicas(service.Deploy), - Strategy: toDeploymentStrategy(service.Deploy), - Template: podTemplate, - }, - }, nil -} - -func selectorLabels(projectName string, serviceName string) map[string]string { - return map[string]string{ - api.ProjectLabel: projectName, - api.ServiceLabel: serviceName, - } -} - -func mapToDaemonset(project *types.Project, service types.ServiceConfig) (*apps.DaemonSet, error) { - labels := selectorLabels(project.Name, service.Name) - podTemplate, err := toPodTemplate(project, service, labels) - if err != nil { - return nil, err - } - - return &apps.DaemonSet{ - ObjectMeta: meta.ObjectMeta{ - Name: service.Name, - Labels: labels, - }, - Spec: apps.DaemonSetSpec{ - Template: podTemplate, - }, - }, nil -} - -func toReplicas(deploy *types.DeployConfig) *int32 { - v := int32(1) - if deploy != nil { - v = int32(*deploy.Replicas) - } - return &v -} - -func toDeploymentStrategy(deploy *types.DeployConfig) apps.DeploymentStrategy { - if deploy == nil || deploy.UpdateConfig == nil { - return apps.DeploymentStrategy{ - Type: apps.RecreateDeploymentStrategyType, - } - } - return apps.DeploymentStrategy{ - Type: apps.RollingUpdateDeploymentStrategyType, - RollingUpdate: &apps.RollingUpdateDeployment{ - MaxUnavailable: &intstr.IntOrString{ - Type: intstr.Int, - IntVal: int32(*deploy.UpdateConfig.Parallelism), - }, - MaxSurge: nil, - }, - } -} - -func mapToPVC(project *types.Project, service types.ServiceConfig, vol types.ServiceVolumeConfig) runtime.Object { - rwaccess := core.ReadWriteOnce - if vol.ReadOnly { - rwaccess = core.ReadOnlyMany - } - return &core.PersistentVolumeClaim{ - TypeMeta: meta.TypeMeta{ - Kind: "PersistentVolumeClaim", - APIVersion: "v1", - }, - ObjectMeta: meta.ObjectMeta{ - Name: vol.Source, - Labels: selectorLabels(project.Name, service.Name), - }, - Spec: core.PersistentVolumeClaimSpec{ - VolumeName: vol.Source, - AccessModes: []core.PersistentVolumeAccessMode{rwaccess}, - Resources: core.ResourceRequirements{ - Requests: core.ResourceList{ - core.ResourceStorage: resource.MustParse("100Mi"), - }, - }, - }, - } -} - -// toSecondsOrDefault converts a duration string in seconds and defaults to a -// given value if the duration is nil. -// The supported units are us, ms, s, m and h. -func toSecondsOrDefault(duration *types.Duration, defaultValue int32) int32 { //nolint: unparam - if duration == nil { - return defaultValue - } - return int32(time.Duration(*duration).Seconds()) -} diff --git a/kube/resources/kube_test.go b/kube/resources/kube_test.go deleted file mode 100644 index fdb6ef06e..000000000 --- a/kube/resources/kube_test.go +++ /dev/null @@ -1,87 +0,0 @@ -// +build kube - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package resources - -import ( - "testing" - - "gotest.tools/v3/assert" - - core "k8s.io/api/core/v1" - meta "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" -) - -func TestServiceWithExposedPort(t *testing.T) { - model, err := loadYAML(` -services: - nginx: - image: nginx - ports: - - "80:80" -`) - assert.NilError(t, err) - - service := mapToService(model, model.Services[0]) - assert.DeepEqual(t, *service, core.Service{ - TypeMeta: meta.TypeMeta{ - Kind: "Service", - APIVersion: "v1", - }, - ObjectMeta: meta.ObjectMeta{ - Name: "nginx", - }, - Spec: core.ServiceSpec{ - Selector: map[string]string{"com.docker.compose.service": "nginx", "com.docker.compose.project": ""}, - Ports: []core.ServicePort{ - { - Name: "80-tcp", - Port: int32(80), - TargetPort: intstr.FromInt(int(80)), - Protocol: core.ProtocolTCP, - }, - }, - Type: core.ServiceTypeLoadBalancer, - }}) -} - -func TestServiceWithoutExposedPort(t *testing.T) { - model, err := loadYAML(` -services: - nginx: - image: nginx -`) - assert.NilError(t, err) - - service := mapToService(model, model.Services[0]) - assert.DeepEqual(t, *service, core.Service{ - TypeMeta: meta.TypeMeta{ - Kind: "Service", - APIVersion: "v1", - }, - ObjectMeta: meta.ObjectMeta{ - Name: "nginx", - }, - Spec: core.ServiceSpec{ - Selector: map[string]string{"com.docker.compose.service": "nginx", "com.docker.compose.project": ""}, - ClusterIP: "None", - Ports: []core.ServicePort{}, - Type: core.ServiceTypeClusterIP, - }}) -} diff --git a/kube/resources/placement.go b/kube/resources/placement.go deleted file mode 100644 index dd761f435..000000000 --- a/kube/resources/placement.go +++ /dev/null @@ -1,144 +0,0 @@ -// +build kube - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package resources - -import ( - "regexp" - "strings" - - "github.com/compose-spec/compose-go/types" - "github.com/pkg/errors" - apiv1 "k8s.io/api/core/v1" -) - -var constraintEquals = regexp.MustCompile(`([\w\.]*)\W*(==|!=)\W*([\w\.]*)`) - -const ( - kubernetesOs = "beta.kubernetes.io/os" - kubernetesArch = "beta.kubernetes.io/arch" - kubernetesHostname = "kubernetes.io/hostname" -) - -// node.id Node ID node.id == 2ivku8v2gvtg4 -// node.hostname Node hostname node.hostname != node-2 -// node.role Node role node.role == manager -// node.labels user defined node labels node.labels.security == high -// engine.labels Docker Engine's labels engine.labels.operatingsystem == ubuntu 14.04 -func toNodeAffinity(deploy *types.DeployConfig) (*apiv1.Affinity, error) { - constraints := []string{} - if deploy != nil && deploy.Placement.Constraints != nil { - constraints = deploy.Placement.Constraints - } - requirements := []apiv1.NodeSelectorRequirement{} - for _, constraint := range constraints { - matches := constraintEquals.FindStringSubmatch(constraint) - if len(matches) == 4 { - key := matches[1] - operator, err := toRequirementOperator(matches[2]) - if err != nil { - return nil, err - } - value := matches[3] - - switch { - case key == constraintOs: - requirements = append(requirements, apiv1.NodeSelectorRequirement{ - Key: kubernetesOs, - Operator: operator, - Values: []string{value}, - }) - case key == constraintArch: - requirements = append(requirements, apiv1.NodeSelectorRequirement{ - Key: kubernetesArch, - Operator: operator, - Values: []string{value}, - }) - case key == constraintHostname: - requirements = append(requirements, apiv1.NodeSelectorRequirement{ - Key: kubernetesHostname, - Operator: operator, - Values: []string{value}, - }) - case strings.HasPrefix(key, constraintLabelPrefix): - requirements = append(requirements, apiv1.NodeSelectorRequirement{ - Key: strings.TrimPrefix(key, constraintLabelPrefix), - Operator: operator, - Values: []string{value}, - }) - } - } - } - - if !hasRequirement(requirements, kubernetesOs) { - requirements = append(requirements, apiv1.NodeSelectorRequirement{ - Key: kubernetesOs, - Operator: apiv1.NodeSelectorOpIn, - Values: []string{"linux"}, - }) - } - if !hasRequirement(requirements, kubernetesArch) { - requirements = append(requirements, apiv1.NodeSelectorRequirement{ - Key: kubernetesArch, - Operator: apiv1.NodeSelectorOpIn, - Values: []string{"amd64"}, - }) - } - return &apiv1.Affinity{ - NodeAffinity: &apiv1.NodeAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: &apiv1.NodeSelector{ - NodeSelectorTerms: []apiv1.NodeSelectorTerm{ - { - MatchExpressions: requirements, - }, - }, - }, - }, - }, nil -} - -const ( - constraintOs = "node.platform.os" - constraintArch = "node.platform.arch" - constraintHostname = "node.hostname" - constraintLabelPrefix = "node.labels." -) - -func hasRequirement(requirements []apiv1.NodeSelectorRequirement, key string) bool { - for _, r := range requirements { - if r.Key == key { - return true - } - } - return false -} - -func toRequirementOperator(sign string) (apiv1.NodeSelectorOperator, error) { - switch sign { - case "==": - return apiv1.NodeSelectorOpIn, nil - case "!=": - return apiv1.NodeSelectorOpNotIn, nil - case ">": - return apiv1.NodeSelectorOpGt, nil - case "<": - return apiv1.NodeSelectorOpLt, nil - default: - return "", errors.Errorf("operator %s not supported", sign) - } -} diff --git a/kube/resources/placement_test.go b/kube/resources/placement_test.go deleted file mode 100644 index 2ca5033e8..000000000 --- a/kube/resources/placement_test.go +++ /dev/null @@ -1,182 +0,0 @@ -// +build kube - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package resources - -import ( - "reflect" - "testing" - - "github.com/compose-spec/compose-go/types" - - "github.com/stretchr/testify/assert" - apiv1 "k8s.io/api/core/v1" -) - -/* FIXME -func TestToPodWithPlacement(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - redis: - image: redis:alpine - deploy: - placement: - constraints: - - node.platform.os == linux - - node.platform.arch == amd64 - - node.hostname == node01 - - node.labels.label1 == value1 - - node.labels.label2.subpath != value2 -`) - - expectedRequirements := []apiv1.NodeSelectorRequirement{ - {Key: "beta.kubernetes.io/os", Operator: apiv1.NodeSelectorOpIn, Values: []string{"linux"}}, - {Key: "beta.kubernetes.io/arch", Operator: apiv1.NodeSelectorOpIn, Values: []string{"amd64"}}, - {Key: "kubernetes.io/hostname", Operator: apiv1.NodeSelectorOpIn, Values: []string{"node01"}}, - {Key: "label1", Operator: apiv1.NodeSelectorOpIn, Values: []string{"value1"}}, - {Key: "label2.subpath", Operator: apiv1.NodeSelectorOpNotIn, Values: []string{"value2"}}, - } - - requirements := podTemplate.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchExpressions - - sort.Slice(expectedRequirements, func(i, j int) bool { return expectedRequirements[i].Key < expectedRequirements[j].Key }) - sort.Slice(requirements, func(i, j int) bool { return requirements[i].Key < requirements[j].Key }) - - assert.EqualValues(t, expectedRequirements, requirements) -} -*/ - -type keyValue struct { - key string - value string -} - -func kv(key, value string) keyValue { - return keyValue{key: key, value: value} -} - -func makeExpectedAffinity(kvs ...keyValue) *apiv1.Affinity { - - var matchExpressions []apiv1.NodeSelectorRequirement - for _, kv := range kvs { - matchExpressions = append( - matchExpressions, - apiv1.NodeSelectorRequirement{ - Key: kv.key, - Operator: apiv1.NodeSelectorOpIn, - Values: []string{kv.value}, - }, - ) - } - return &apiv1.Affinity{ - NodeAffinity: &apiv1.NodeAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: &apiv1.NodeSelector{ - NodeSelectorTerms: []apiv1.NodeSelectorTerm{ - { - MatchExpressions: matchExpressions, - }, - }, - }, - }, - } -} - -func TestNodeAfinity(t *testing.T) { - cases := []struct { - name string - source []string - expected *apiv1.Affinity - }{ - { - name: "nil", - expected: makeExpectedAffinity( - kv(kubernetesOs, "linux"), - kv(kubernetesArch, "amd64"), - ), - }, - { - name: "hostname", - source: []string{"node.hostname == test"}, - expected: makeExpectedAffinity( - kv(kubernetesHostname, "test"), - kv(kubernetesOs, "linux"), - kv(kubernetesArch, "amd64"), - ), - }, - { - name: "os", - source: []string{"node.platform.os == windows"}, - expected: makeExpectedAffinity( - kv(kubernetesOs, "windows"), - kv(kubernetesArch, "amd64"), - ), - }, - { - name: "arch", - source: []string{"node.platform.arch == arm64"}, - expected: makeExpectedAffinity( - kv(kubernetesArch, "arm64"), - kv(kubernetesOs, "linux"), - ), - }, - { - name: "custom-labels", - source: []string{"node.platform.os == windows", "node.platform.arch == arm64"}, - expected: makeExpectedAffinity( - kv(kubernetesArch, "arm64"), - kv(kubernetesOs, "windows"), - ), - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - result, err := toNodeAffinity(&types.DeployConfig{ - Placement: types.Placement{ - Constraints: c.source, - }, - }) - assert.NoError(t, err) - assert.True(t, nodeAffinityMatch(c.expected, result)) - }) - } -} - -func nodeSelectorRequirementsToMap(source []apiv1.NodeSelectorRequirement, result map[string]apiv1.NodeSelectorRequirement) { - for _, t := range source { - result[t.Key] = t - } -} - -func nodeAffinityMatch(expected, actual *apiv1.Affinity) bool { - expectedTerms := expected.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms - actualTerms := actual.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms - expectedExpressions := make(map[string]apiv1.NodeSelectorRequirement) - expectedFields := make(map[string]apiv1.NodeSelectorRequirement) - actualExpressions := make(map[string]apiv1.NodeSelectorRequirement) - actualFields := make(map[string]apiv1.NodeSelectorRequirement) - for _, v := range expectedTerms { - nodeSelectorRequirementsToMap(v.MatchExpressions, expectedExpressions) - nodeSelectorRequirementsToMap(v.MatchFields, expectedFields) - } - for _, v := range actualTerms { - nodeSelectorRequirementsToMap(v.MatchExpressions, actualExpressions) - nodeSelectorRequirementsToMap(v.MatchFields, actualFields) - } - return reflect.DeepEqual(expectedExpressions, actualExpressions) && reflect.DeepEqual(expectedFields, actualFields) -} diff --git a/kube/resources/pod.go b/kube/resources/pod.go deleted file mode 100644 index ebb7873d4..000000000 --- a/kube/resources/pod.go +++ /dev/null @@ -1,343 +0,0 @@ -// +build kube - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package resources - -import ( - "fmt" - "sort" - "strconv" - "strings" - "time" - - "github.com/compose-spec/compose-go/types" - "github.com/docker/docker/api/types/swarm" - - "github.com/pkg/errors" - apiv1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func toPodTemplate(project *types.Project, serviceConfig types.ServiceConfig, labels map[string]string) (apiv1.PodTemplateSpec, error) { - tpl := apiv1.PodTemplateSpec{} - //nodeAffinity, err := toNodeAffinity(serviceConfig.Deploy) - //if err != nil { - // return apiv1.PodTemplateSpec{}, err - //} - hostAliases, err := toHostAliases(serviceConfig.ExtraHosts) - if err != nil { - return apiv1.PodTemplateSpec{}, err - } - env, err := toEnv(serviceConfig.Environment) - if err != nil { - return apiv1.PodTemplateSpec{}, err - } - restartPolicy, err := toRestartPolicy(serviceConfig) - if err != nil { - return apiv1.PodTemplateSpec{}, err - } - - var limits apiv1.ResourceList - if serviceConfig.Deploy != nil && serviceConfig.Deploy.Resources.Limits != nil { - limits, err = toResource(serviceConfig.Deploy.Resources.Limits) - if err != nil { - return apiv1.PodTemplateSpec{}, err - } - } - var requests apiv1.ResourceList - if serviceConfig.Deploy != nil && serviceConfig.Deploy.Resources.Reservations != nil { - requests, err = toResource(serviceConfig.Deploy.Resources.Reservations) - if err != nil { - return apiv1.PodTemplateSpec{}, err - } - } - - volumes, err := toVolumes(project, serviceConfig) - if err != nil { - return apiv1.PodTemplateSpec{}, err - } - volumeMounts, err := toVolumeMounts(project, serviceConfig) - if err != nil { - return apiv1.PodTemplateSpec{}, err - } - /* pullPolicy, err := toImagePullPolicy(serviceConfig.Image, x-kubernetes-pull-policy) - if err != nil { - return apiv1.PodTemplateSpec{}, err - } */ - tpl.ObjectMeta = metav1.ObjectMeta{ - Labels: labels, - Annotations: serviceConfig.Labels, - } - tpl.Spec.RestartPolicy = restartPolicy - tpl.Spec.Volumes = volumes - tpl.Spec.HostPID = toHostPID(serviceConfig.Pid) - tpl.Spec.HostIPC = toHostIPC(serviceConfig.Ipc) - tpl.Spec.Hostname = serviceConfig.Hostname - tpl.Spec.TerminationGracePeriodSeconds = toTerminationGracePeriodSeconds(serviceConfig.StopGracePeriod) - tpl.Spec.HostAliases = hostAliases - //tpl.Spec.Affinity = nodeAffinity - // we dont want to remove all containers and recreate them because: - // an admission plugin can add sidecar containers - // we for sure want to keep the main container to be additive - if len(tpl.Spec.Containers) == 0 { - tpl.Spec.Containers = []apiv1.Container{{}} - } - - containerIX := 0 - for ix, c := range tpl.Spec.Containers { - if c.Name == serviceConfig.Name { - containerIX = ix - break - } - } - tpl.Spec.Containers[containerIX].Name = serviceConfig.Name - tpl.Spec.Containers[containerIX].Image = serviceConfig.Image - // FIXME tpl.Spec.Containers[containerIX].ImagePullPolicy = pullPolicy - tpl.Spec.Containers[containerIX].Command = serviceConfig.Entrypoint - tpl.Spec.Containers[containerIX].Args = serviceConfig.Command - tpl.Spec.Containers[containerIX].WorkingDir = serviceConfig.WorkingDir - tpl.Spec.Containers[containerIX].TTY = serviceConfig.Tty - tpl.Spec.Containers[containerIX].Stdin = serviceConfig.StdinOpen - tpl.Spec.Containers[containerIX].Ports = toPorts(serviceConfig.Ports) - tpl.Spec.Containers[containerIX].LivenessProbe = toLivenessProbe(serviceConfig.HealthCheck) - tpl.Spec.Containers[containerIX].Env = env - tpl.Spec.Containers[containerIX].VolumeMounts = volumeMounts - tpl.Spec.Containers[containerIX].SecurityContext = toSecurityContext(serviceConfig) - tpl.Spec.Containers[containerIX].Resources = apiv1.ResourceRequirements{ - Limits: limits, - Requests: requests, - } - - /* FIXME - if serviceConfig.PullSecret != "" { - pullSecrets := map[string]struct{}{} - for _, ps := range tpl.Spec.ImagePullSecrets { - pullSecrets[ps.Name] = struct{}{} - } - if _, ok := pullSecrets[serviceConfig.PullSecret]; !ok { - tpl.Spec.ImagePullSecrets = append(tpl.Spec.ImagePullSecrets, apiv1.LocalObjectReference{Name: serviceConfig.PullSecret}) - } - } - */ - return tpl, nil -} - -func toHostAliases(extraHosts []string) ([]apiv1.HostAlias, error) { - if extraHosts == nil { - return nil, nil - } - - byHostnames := map[string]string{} - for _, host := range extraHosts { - split := strings.SplitN(host, ":", 2) - if len(split) != 2 { - return nil, errors.Errorf("malformed host %s", host) - } - byHostnames[split[0]] = split[1] - } - - byIPs := map[string][]string{} - for k, v := range byHostnames { - byIPs[v] = append(byIPs[v], k) - } - - aliases := make([]apiv1.HostAlias, len(byIPs)) - i := 0 - for key, hosts := range byIPs { - sort.Strings(hosts) - aliases[i] = apiv1.HostAlias{ - IP: key, - Hostnames: hosts, - } - i++ - } - sort.Slice(aliases, func(i, j int) bool { return aliases[i].IP < aliases[j].IP }) - return aliases, nil -} - -func toHostPID(pid string) bool { - return "host" == pid -} - -func toHostIPC(ipc string) bool { - return "host" == ipc -} - -func toTerminationGracePeriodSeconds(duration *types.Duration) *int64 { - if duration == nil { - return nil - } - gracePeriod := int64(time.Duration(*duration).Seconds()) - return &gracePeriod -} - -func toLivenessProbe(hc *types.HealthCheckConfig) *apiv1.Probe { - if hc == nil || len(hc.Test) < 1 || hc.Test[0] == "NONE" { - return nil - } - - command := hc.Test[1:] - if hc.Test[0] == "CMD-SHELL" { - command = append([]string{"sh", "-c"}, command...) - } - - return &apiv1.Probe{ - TimeoutSeconds: toSecondsOrDefault(hc.Timeout, 1), - PeriodSeconds: toSecondsOrDefault(hc.Interval, 1), - FailureThreshold: int32(defaultUint64(hc.Retries, 3)), - Handler: apiv1.Handler{ - Exec: &apiv1.ExecAction{ - Command: command, - }, - }, - } -} - -func toEnv(env map[string]*string) ([]apiv1.EnvVar, error) { - var envVars []apiv1.EnvVar - - for k, v := range env { - if v == nil { - return nil, errors.Errorf("%s has no value, unsetting an environment variable is not supported", k) - } - envVars = append(envVars, toEnvVar(k, *v)) - } - sort.Slice(envVars, func(i, j int) bool { return envVars[i].Name < envVars[j].Name }) - return envVars, nil -} - -func toEnvVar(key, value string) apiv1.EnvVar { - return apiv1.EnvVar{ - Name: key, - Value: value, - } -} - -func toPorts(list []types.ServicePortConfig) []apiv1.ContainerPort { - var ports []apiv1.ContainerPort - - for _, v := range list { - ports = append(ports, apiv1.ContainerPort{ - ContainerPort: int32(v.Target), - Protocol: toProtocol(v.Protocol), - }) - } - - return ports -} - -func toProtocol(value string) apiv1.Protocol { - if value == "udp" { - return apiv1.ProtocolUDP - } - return apiv1.ProtocolTCP -} - -func toRestartPolicy(s types.ServiceConfig) (apiv1.RestartPolicy, error) { - if s.Deploy == nil || s.Deploy.RestartPolicy == nil { - return apiv1.RestartPolicyAlways, nil - } - policy := s.Deploy.RestartPolicy - - switch policy.Condition { - case string(swarm.RestartPolicyConditionAny): - return apiv1.RestartPolicyAlways, nil - case string(swarm.RestartPolicyConditionNone): - return apiv1.RestartPolicyNever, nil - case string(swarm.RestartPolicyConditionOnFailure): - return apiv1.RestartPolicyOnFailure, nil - default: - return "", errors.Errorf("unsupported restart policy %s", policy.Condition) - } -} - -func toResource(res *types.Resource) (apiv1.ResourceList, error) { - list := make(apiv1.ResourceList) - if res.NanoCPUs != "" { - cpus, err := resource.ParseQuantity(res.NanoCPUs) - if err != nil { - return nil, err - } - list[apiv1.ResourceCPU] = cpus - } - if res.MemoryBytes != 0 { - memory, err := resource.ParseQuantity(fmt.Sprintf("%v", res.MemoryBytes)) - if err != nil { - return nil, err - } - list[apiv1.ResourceMemory] = memory - } - return list, nil -} - -func toSecurityContext(s types.ServiceConfig) *apiv1.SecurityContext { - isPrivileged := toBoolPointer(s.Privileged) - isReadOnly := toBoolPointer(s.ReadOnly) - - var capabilities *apiv1.Capabilities - if s.CapAdd != nil || s.CapDrop != nil { - capabilities = &apiv1.Capabilities{ - Add: toCapabilities(s.CapAdd), - Drop: toCapabilities(s.CapDrop), - } - } - - var userID *int64 - if s.User != "" { - numerical, err := strconv.Atoi(s.User) - if err == nil { - unixUserID := int64(numerical) - userID = &unixUserID - } - } - - if isPrivileged == nil && isReadOnly == nil && capabilities == nil && userID == nil { - return nil - } - - return &apiv1.SecurityContext{ - RunAsUser: userID, - Privileged: isPrivileged, - ReadOnlyRootFilesystem: isReadOnly, - Capabilities: capabilities, - } -} - -func toBoolPointer(value bool) *bool { - if value { - return &value - } - - return nil -} - -func defaultUint64(v *uint64, defaultValue uint64) uint64 { //nolint: unparam - if v == nil { - return defaultValue - } - - return *v -} - -func toCapabilities(list []string) (capabilities []apiv1.Capability) { - for _, c := range list { - capabilities = append(capabilities, apiv1.Capability(c)) - } - return -} diff --git a/kube/resources/pod_test.go b/kube/resources/pod_test.go deleted file mode 100644 index 4970fc259..000000000 --- a/kube/resources/pod_test.go +++ /dev/null @@ -1,1033 +0,0 @@ -// +build kube - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package resources - -import ( - "fmt" - "os" - "runtime" - "testing" - - "github.com/compose-spec/compose-go/loader" - "github.com/compose-spec/compose-go/types" - "github.com/stretchr/testify/assert" - apiv1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" -) - -func loadYAML(yaml string) (*types.Project, error) { - dict, err := loader.ParseYAML([]byte(yaml)) - if err != nil { - return nil, err - } - workingDir, err := os.Getwd() - if err != nil { - panic(err) - } - configs := []types.ConfigFile{ - { - Filename: "test-compose.yaml", - Config: dict, - }, - } - config := types.ConfigDetails{ - WorkingDir: workingDir, - ConfigFiles: configs, - Environment: nil, - } - return loader.Load(config) -} - -func podTemplate(t *testing.T, yaml string) apiv1.PodTemplateSpec { - res, err := podTemplateWithError(yaml) - assert.NoError(t, err) - return res -} - -func podTemplateWithError(yaml string) (apiv1.PodTemplateSpec, error) { - model, err := loadYAML(yaml) - if err != nil { - return apiv1.PodTemplateSpec{}, err - } - - return toPodTemplate(model, model.Services[0], nil) -} - -func TestToPodWithDockerSocket(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("on windows, source path validation is broken (and actually, source validation for windows workload is broken too). Skip it for now, as we don't support it yet") - return - } - podTemplate := podTemplate(t, ` -version: "3" -services: - redis: - image: "redis:alpine" - volumes: - - "/var/run/docker.sock:/var/run/docker.sock" -`) - - expectedVolume := apiv1.Volume{ - Name: "mount-0", - VolumeSource: apiv1.VolumeSource{ - HostPath: &apiv1.HostPathVolumeSource{ - Path: "/var/run", - }, - }, - } - - expectedMount := apiv1.VolumeMount{ - Name: "mount-0", - MountPath: "/var/run/docker.sock", - SubPath: "docker.sock", - } - - assert.Len(t, podTemplate.Spec.Volumes, 1) - assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) - assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) - assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) -} - -func TestToPodWithFunkyCommand(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - redis: - image: basi/node-exporter - command: ["-collector.procfs", "/host/proc", "-collector.sysfs", "/host/sys"] -`) - - expectedArgs := []string{ - `-collector.procfs`, - `/host/proc`, // ? - `-collector.sysfs`, - `/host/sys`, // ? - } - assert.Equal(t, expectedArgs, podTemplate.Spec.Containers[0].Args) -} - -/* FIXME -func TestToPodWithGlobalVolume(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - db: - image: "postgres:9.4" - volumes: - - dbdata:/var/lib/postgresql/data -volumes: - dbdata: -`) - - expectedMount := apiv1.VolumeMount{ - Name: "dbdata", - MountPath: "/var/lib/postgresql/data", - } - assert.Len(t, podTemplate.Spec.Volumes, 0) - assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) - assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) -} -*/ - -func TestToPodWithResources(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - db: - image: "postgres:9.4" - deploy: - resources: - limits: - cpus: "0.001" - memory: 50Mb - reservations: - cpus: "0.0001" - memory: 20Mb -`) - - expectedResourceRequirements := apiv1.ResourceRequirements{ - Limits: map[apiv1.ResourceName]resource.Quantity{ - apiv1.ResourceCPU: resource.MustParse("0.001"), - apiv1.ResourceMemory: resource.MustParse(fmt.Sprintf("%d", 50*1024*1024)), - }, - Requests: map[apiv1.ResourceName]resource.Quantity{ - apiv1.ResourceCPU: resource.MustParse("0.0001"), - apiv1.ResourceMemory: resource.MustParse(fmt.Sprintf("%d", 20*1024*1024)), - }, - } - assert.Equal(t, expectedResourceRequirements, podTemplate.Spec.Containers[0].Resources) -} - -func TestToPodWithCapabilities(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - redis: - image: "redis:alpine" - cap_add: - - ALL - cap_drop: - - NET_ADMIN - - SYS_ADMIN -`) - - expectedSecurityContext := &apiv1.SecurityContext{ - Capabilities: &apiv1.Capabilities{ - Add: []apiv1.Capability{"ALL"}, - Drop: []apiv1.Capability{"NET_ADMIN", "SYS_ADMIN"}, - }, - } - - assert.Equal(t, expectedSecurityContext, podTemplate.Spec.Containers[0].SecurityContext) -} - -func TestToPodWithReadOnly(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - redis: - image: "redis:alpine" - read_only: true -`) - - yes := true - expectedSecurityContext := &apiv1.SecurityContext{ - ReadOnlyRootFilesystem: &yes, - } - assert.Equal(t, expectedSecurityContext, podTemplate.Spec.Containers[0].SecurityContext) -} - -func TestToPodWithPrivileged(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - redis: - image: "redis:alpine" - privileged: true -`) - - yes := true - expectedSecurityContext := &apiv1.SecurityContext{ - Privileged: &yes, - } - assert.Equal(t, expectedSecurityContext, podTemplate.Spec.Containers[0].SecurityContext) -} - -func TestToPodWithEnvNilShouldErrorOut(t *testing.T) { - _, err := podTemplateWithError(` -version: "3" -services: - redis: - image: "redis:alpine" - environment: - - SESSION_SECRET -`) - assert.Error(t, err) -} - -func TestToPodWithEnv(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - redis: - image: "redis:alpine" - environment: - - RACK_ENV=development - - SHOW=true -`) - - expectedEnv := []apiv1.EnvVar{ - { - Name: "RACK_ENV", - Value: "development", - }, - { - Name: "SHOW", - Value: "true", - }, - } - - assert.Equal(t, expectedEnv, podTemplate.Spec.Containers[0].Env) -} - -func TestToPodWithVolume(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("on windows, source path validation is broken (and actually, source validation for windows workload is broken too). Skip it for now, as we don't support it yet") - return - } - podTemplate := podTemplate(t, ` -version: "3" -services: - nginx: - image: nginx - volumes: - - /ignore:/ignore - - /opt/data:/var/lib/mysql:ro -`) - - assert.Len(t, podTemplate.Spec.Volumes, 2) - assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 2) -} - -/* FIXME -func TestToPodWithRelativeVolumes(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("on windows, source path validation is broken (and actually, source validation for windows workload is broken too). Skip it for now, as we don't support it yet") - return - } - _, err := podTemplateWithError(` -version: "3" -services: - nginx: - image: nginx - volumes: - - ./fail:/ignore -`) - - assert.Error(t, err) -} -*/ - -func TestToPodWithHealthCheck(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - nginx: - image: nginx - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost"] - interval: 90s - timeout: 10s - retries: 3 -`) - - expectedLivenessProbe := &apiv1.Probe{ - TimeoutSeconds: 10, - PeriodSeconds: 90, - FailureThreshold: 3, - Handler: apiv1.Handler{ - Exec: &apiv1.ExecAction{ - Command: []string{"curl", "-f", "http://localhost"}, - }, - }, - } - - assert.Equal(t, expectedLivenessProbe, podTemplate.Spec.Containers[0].LivenessProbe) -} - -func TestToPodWithShellHealthCheck(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - nginx: - image: nginx - healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost"] -`) - - expectedLivenessProbe := &apiv1.Probe{ - TimeoutSeconds: 1, - PeriodSeconds: 1, - FailureThreshold: 3, - Handler: apiv1.Handler{ - Exec: &apiv1.ExecAction{ - Command: []string{"sh", "-c", "curl -f http://localhost"}, - }, - }, - } - - assert.Equal(t, expectedLivenessProbe, podTemplate.Spec.Containers[0].LivenessProbe) -} - -/* FIXME -func TestToPodWithTargetlessExternalSecret(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - nginx: - image: nginx - secrets: - - my_secret -`) - - expectedVolume := apiv1.Volume{ - Name: "secret-0", - VolumeSource: apiv1.VolumeSource{ - Secret: &apiv1.SecretVolumeSource{ - SecretName: "my_secret", - Items: []apiv1.KeyToPath{ - { - Key: "file", // TODO: This is the key we assume external secrets use - Path: "secret-0", - }, - }, - }, - }, - } - - expectedMount := apiv1.VolumeMount{ - Name: "secret-0", - ReadOnly: true, - MountPath: "/run/secrets/my_secret", - SubPath: "secret-0", - } - - assert.Len(t, podTemplate.Spec.Volumes, 1) - assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) - assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) - assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) -} -*/ - -/* FIXME -func TestToPodWithExternalSecret(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - nginx: - image: nginx - secrets: - - source: my_secret - target: nginx_secret -`) - - expectedVolume := apiv1.Volume{ - Name: "secret-0", - VolumeSource: apiv1.VolumeSource{ - Secret: &apiv1.SecretVolumeSource{ - SecretName: "my_secret", - Items: []apiv1.KeyToPath{ - { - Key: "file", // TODO: This is the key we assume external secrets use - Path: "secret-0", - }, - }, - }, - }, - } - - expectedMount := apiv1.VolumeMount{ - Name: "secret-0", - ReadOnly: true, - MountPath: "/run/secrets/nginx_secret", - SubPath: "secret-0", - } - - assert.Len(t, podTemplate.Spec.Volumes, 1) - assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) - assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) - assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) -} -*/ - -/* FIXME -func TestToPodWithFileBasedSecret(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - nginx: - image: nginx - secrets: - - source: my_secret -secrets: - my_secret: - file: ./secret.txt -`) - - expectedVolume := apiv1.Volume{ - Name: "secret-0", - VolumeSource: apiv1.VolumeSource{ - Secret: &apiv1.SecretVolumeSource{ - SecretName: "my_secret", - Items: []apiv1.KeyToPath{ - { - Key: "secret.txt", - Path: "secret-0", - }, - }, - }, - }, - } - - expectedMount := apiv1.VolumeMount{ - Name: "secret-0", - ReadOnly: true, - MountPath: "/run/secrets/my_secret", - SubPath: "secret-0", - } - - assert.Len(t, podTemplate.Spec.Volumes, 1) - assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) - assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) - assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) -} -*/ - -/* FIXME -func TestToPodWithTwoFileBasedSecrets(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - nginx: - image: nginx - secrets: - - source: my_secret1 - - source: my_secret2 - target: secret2 -secrets: - my_secret1: - file: ./secret1.txt - my_secret2: - file: ./secret2.txt -`) - - expectedVolumes := []apiv1.Volume{ - { - Name: "secret-0", - VolumeSource: apiv1.VolumeSource{ - Secret: &apiv1.SecretVolumeSource{ - SecretName: "my_secret1", - Items: []apiv1.KeyToPath{ - { - Key: "secret1.txt", - Path: "secret-0", - }, - }, - }, - }, - }, - { - Name: "secret-1", - VolumeSource: apiv1.VolumeSource{ - Secret: &apiv1.SecretVolumeSource{ - SecretName: "my_secret2", - Items: []apiv1.KeyToPath{ - { - Key: "secret2.txt", - Path: "secret-1", - }, - }, - }, - }, - }, - } - - expectedMounts := []apiv1.VolumeMount{ - { - Name: "secret-0", - ReadOnly: true, - MountPath: "/run/secrets/my_secret1", - SubPath: "secret-0", - }, - { - Name: "secret-1", - ReadOnly: true, - MountPath: "/run/secrets/secret2", - SubPath: "secret-1", - }, - } - - assert.Equal(t, expectedVolumes, podTemplate.Spec.Volumes) - assert.Equal(t, expectedMounts, podTemplate.Spec.Containers[0].VolumeMounts) -} -*/ - -func TestToPodWithTerminationGracePeriod(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - redis: - image: "redis:alpine" - stop_grace_period: 100s -`) - - expected := int64(100) - assert.Equal(t, &expected, podTemplate.Spec.TerminationGracePeriodSeconds) -} - -func TestToPodWithTmpfs(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - redis: - image: "redis:alpine" - tmpfs: - - /tmp -`) - - expectedVolume := apiv1.Volume{ - Name: "tmp-0", - VolumeSource: apiv1.VolumeSource{ - EmptyDir: &apiv1.EmptyDirVolumeSource{ - Medium: "Memory", - }, - }, - } - - expectedMount := apiv1.VolumeMount{ - Name: "tmp-0", - MountPath: "/tmp", - } - - assert.Len(t, podTemplate.Spec.Volumes, 1) - assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) - assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) - assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) -} - -func TestToPodWithNumericalUser(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - redis: - image: "redis:alpine" - user: "1000" -`) - - userID := int64(1000) - - expectedSecurityContext := &apiv1.SecurityContext{ - RunAsUser: &userID, - } - - assert.Equal(t, expectedSecurityContext, podTemplate.Spec.Containers[0].SecurityContext) -} - -func TestToPodWithGitVolume(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - redis: - image: "redis:alpine" - volumes: - - source: "git@github.com:moby/moby.git" - target: /sources - type: git -`) - - expectedVolume := apiv1.Volume{ - Name: "mount-0", - VolumeSource: apiv1.VolumeSource{ - GitRepo: &apiv1.GitRepoVolumeSource{ - Repository: "git@github.com:moby/moby.git", - }, - }, - } - - expectedMount := apiv1.VolumeMount{ - Name: "mount-0", - ReadOnly: false, - MountPath: "/sources", - } - - assert.Len(t, podTemplate.Spec.Volumes, 1) - assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) - assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) - assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) -} - -/* FIXME -func TestToPodWithFileBasedConfig(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - redis: - image: "redis:alpine" - configs: - - source: my_config - target: /usr/share/nginx/html/index.html - uid: "103" - gid: "103" - mode: 0440 -configs: - my_config: - file: ./file.html -`) - - mode := int32(0440) - - expectedVolume := apiv1.Volume{ - Name: "config-0", - VolumeSource: apiv1.VolumeSource{ - ConfigMap: &apiv1.ConfigMapVolumeSource{ - LocalObjectReference: apiv1.LocalObjectReference{ - Name: "my_config", - }, - Items: []apiv1.KeyToPath{ - { - Key: "file.html", - Path: "config-0", - Mode: &mode, - }, - }, - }, - }, - } - - expectedMount := apiv1.VolumeMount{ - Name: "config-0", - ReadOnly: true, - MountPath: "/usr/share/nginx/html/index.html", - SubPath: "config-0", - } - - assert.Len(t, podTemplate.Spec.Volumes, 1) - assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) - assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) - assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) -} -*/ - -/* FIXME -func TestToPodWithTargetlessFileBasedConfig(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - redis: - image: "redis:alpine" - configs: - - my_config -configs: - my_config: - file: ./file.html -`) - - expectedVolume := apiv1.Volume{ - Name: "config-0", - VolumeSource: apiv1.VolumeSource{ - ConfigMap: &apiv1.ConfigMapVolumeSource{ - LocalObjectReference: apiv1.LocalObjectReference{ - Name: "myconfig", - }, - Items: []apiv1.KeyToPath{ - { - Key: "file.html", - Path: "config-0", - }, - }, - }, - }, - } - - expectedMount := apiv1.VolumeMount{ - Name: "config-0", - ReadOnly: true, - MountPath: "/myconfig", - SubPath: "config-0", - } - - assert.Len(t, podTemplate.Spec.Volumes, 1) - assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) - assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) - assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) -} -*/ - -func TestToPodWithExternalConfig(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - redis: - image: "redis:alpine" - configs: - - source: my_config - target: /usr/share/nginx/html/index.html - uid: "103" - gid: "103" - mode: 0440 -configs: - my_config: - external: true -`) - - mode := int32(0440) - - expectedVolume := apiv1.Volume{ - Name: "config-0", - VolumeSource: apiv1.VolumeSource{ - ConfigMap: &apiv1.ConfigMapVolumeSource{ - LocalObjectReference: apiv1.LocalObjectReference{ - Name: "my_config", - }, - Items: []apiv1.KeyToPath{ - { - Key: "file", // TODO: This is the key we assume external config use - Path: "config-0", - Mode: &mode, - }, - }, - }, - }, - } - - expectedMount := apiv1.VolumeMount{ - Name: "config-0", - ReadOnly: true, - MountPath: "/usr/share/nginx/html/index.html", - SubPath: "config-0", - } - - assert.Len(t, podTemplate.Spec.Volumes, 1) - assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) - assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) - assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) -} - -/* FIXME -func TestToPodWithTwoConfigsSameMountPoint(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - nginx: - image: nginx - configs: - - source: first - target: /data/first.json - mode: "0440" - - source: second - target: /data/second.json - mode: "0550" -configs: - first: - file: ./file1 - secondv: - file: ./file2 -`) - - mode0440 := int32(0440) - mode0550 := int32(0550) - - expectedVolumes := []apiv1.Volume{ - { - Name: "config-0", - VolumeSource: apiv1.VolumeSource{ - ConfigMap: &apiv1.ConfigMapVolumeSource{ - LocalObjectReference: apiv1.LocalObjectReference{ - Name: "first", - }, - Items: []apiv1.KeyToPath{ - { - Key: "file1", - Path: "config-0", - Mode: &mode0440, - }, - }, - }, - }, - }, - { - Name: "config-1", - VolumeSource: apiv1.VolumeSource{ - ConfigMap: &apiv1.ConfigMapVolumeSource{ - LocalObjectReference: apiv1.LocalObjectReference{ - Name: "second", - }, - Items: []apiv1.KeyToPath{ - { - Key: "file2", - Path: "config-1", - Mode: &mode0550, - }, - }, - }, - }, - }, - } - - expectedMounts := []apiv1.VolumeMount{ - { - Name: "config-0", - ReadOnly: true, - MountPath: "/data/first.json", - SubPath: "config-0", - }, - { - Name: "config-1", - ReadOnly: true, - MountPath: "/data/second.json", - SubPath: "config-1", - }, - } - - assert.Equal(t, expectedVolumes, podTemplate.Spec.Volumes) - assert.Equal(t, expectedMounts, podTemplate.Spec.Containers[0].VolumeMounts) -} -*/ - -func TestToPodWithTwoExternalConfigsSameMountPoint(t *testing.T) { - podTemplate := podTemplate(t, ` -version: "3" -services: - nginx: - image: nginx - configs: - - source: first - target: /data/first.json - - source: second - target: /data/second.json -configs: - first: - file: ./file1 - second: - file: ./file2 -`) - - expectedVolumes := []apiv1.Volume{ - { - Name: "config-0", - VolumeSource: apiv1.VolumeSource{ - ConfigMap: &apiv1.ConfigMapVolumeSource{ - LocalObjectReference: apiv1.LocalObjectReference{ - Name: "first", - }, - Items: []apiv1.KeyToPath{ - { - Key: "file", - Path: "config-0", - }, - }, - }, - }, - }, - { - Name: "config-1", - VolumeSource: apiv1.VolumeSource{ - ConfigMap: &apiv1.ConfigMapVolumeSource{ - LocalObjectReference: apiv1.LocalObjectReference{ - Name: "second", - }, - Items: []apiv1.KeyToPath{ - { - Key: "file", - Path: "config-1", - }, - }, - }, - }, - }, - } - - expectedMounts := []apiv1.VolumeMount{ - { - Name: "config-0", - ReadOnly: true, - MountPath: "/data/first.json", - SubPath: "config-0", - }, - { - Name: "config-1", - ReadOnly: true, - MountPath: "/data/second.json", - SubPath: "config-1", - }, - } - - assert.Equal(t, expectedVolumes, podTemplate.Spec.Volumes) - assert.Equal(t, expectedMounts, podTemplate.Spec.Containers[0].VolumeMounts) -} - -/* FIXME -func TestToPodWithPullSecret(t *testing.T) { - podTemplateWithSecret := podTemplate(t, ` -version: "3" -services: - nginx: - image: nginx - x-kubernetes.pull-secret: test-pull-secret -`) - - assert.Equal(t, 1, len(podTemplateWithSecret.Spec.ImagePullSecrets)) - assert.Equal(t, "test-pull-secret", podTemplateWithSecret.Spec.ImagePullSecrets[0].Name) - - podTemplateNoSecret := podTemplate(t, ` -version: "3" -services: - nginx: - image: nginx -`) - - assert.Nil(t, podTemplateNoSecret.Spec.ImagePullSecrets) -} -*/ - -/* FIXME -func TestToPodWithPullPolicy(t *testing.T) { - cases := []struct { - name string - stack string - expectedPolicy apiv1.PullPolicy - expectedError string - }{ - { - name: "specific tag", - stack: ` -version: "3" -services: - nginx: - image: nginx:specific -`, - expectedPolicy: apiv1.PullIfNotPresent, - }, - { - name: "latest tag", - stack: ` -version: "3" -services: - nginx: - image: nginx:latest -`, - expectedPolicy: apiv1.PullAlways, - }, - { - name: "explicit policy", - stack: ` -version: "3" -services: - nginx: - image: nginx:specific - x-kubernetes.pull-policy: Never -`, - expectedPolicy: apiv1.PullNever, - }, - { - name: "invalid policy", - stack: ` -version: "3" -services: - nginx: - image: nginx:specific - x-kubernetes.pull-policy: Invalid -`, - expectedError: `invalid pull policy "Invalid", must be "Always", "IfNotPresent" or "Never"`, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - pod, err := podTemplateWithError(c.stack) - if c.expectedError != "" { - assert.EqualError(t, err, c.expectedError) - } else { - assert.NoError(t, err) - assert.Equal(t, pod.Spec.Containers[0].ImagePullPolicy, c.expectedPolicy) - } - }) - } -} -*/ diff --git a/kube/resources/secrets.go b/kube/resources/secrets.go deleted file mode 100644 index 3951eb3f9..000000000 --- a/kube/resources/secrets.go +++ /dev/null @@ -1,58 +0,0 @@ -// +build kube - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package resources - -import ( - "io/ioutil" - "strings" - - "github.com/compose-spec/compose-go/types" - - corev1 "k8s.io/api/core/v1" -) - -func toSecretSpecs(project *types.Project) ([]corev1.Secret, error) { - var secrets []corev1.Secret - - for _, s := range project.Secrets { - if s.External.External { - continue - } - name := strings.ReplaceAll(s.Name, "_", "-") - // load secret file content - sensitiveData, err := ioutil.ReadFile(s.File) - if err != nil { - return nil, err - } - - readOnly := true - secret := corev1.Secret{} - secret.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("Secret")) - secret.Name = name - secret.Type = "compose" - secret.Data = map[string][]byte{ - name: sensitiveData, - } - secret.Immutable = &readOnly - - secrets = append(secrets, secret) - } - - return secrets, nil -} diff --git a/kube/resources/volumes.go b/kube/resources/volumes.go deleted file mode 100644 index 2d7f2e95f..000000000 --- a/kube/resources/volumes.go +++ /dev/null @@ -1,241 +0,0 @@ -// +build kube - -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package resources - -import ( - "fmt" - "path" - "path/filepath" - "strings" - - "github.com/compose-spec/compose-go/types" - - "github.com/pkg/errors" - apiv1 "k8s.io/api/core/v1" -) - -const dockerSock = "/var/run/docker.sock" - -type volumeSpec struct { - mount apiv1.VolumeMount - source *apiv1.VolumeSource -} - -func toVolumeSpecs(project *types.Project, s types.ServiceConfig) ([]volumeSpec, error) { - var specs []volumeSpec - for i, m := range s.Volumes { - var source *apiv1.VolumeSource - name := fmt.Sprintf("mount-%d", i) - subpath := "" - if m.Source == dockerSock && m.Target == dockerSock { - subpath = "docker.sock" - source = hostPathVolume("/var/run") - } else if strings.HasSuffix(m.Source, ".git") { - source = gitVolume(m.Source) - } else if m.Type == "volume" { - if m.Source != "" { - name = strings.ReplaceAll(m.Source, "_", "-") - } - } else { - // bind mount - if !filepath.IsAbs(m.Source) { - return nil, errors.Errorf("%s: only absolute paths can be specified in mount source", m.Source) - } - if m.Source == "/" { - source = hostPathVolume("/") - } else { - parent, file := filepath.Split(m.Source) - if parent != "/" { - parent = strings.TrimSuffix(parent, "/") - } - source = hostPathVolume(parent) - subpath = file - } - } - - specs = append(specs, volumeSpec{ - source: source, - mount: volumeMount(name, m.Target, m.ReadOnly, subpath), - }) - } - - for i, m := range s.Tmpfs { - name := fmt.Sprintf("tmp-%d", i) - - specs = append(specs, volumeSpec{ - source: emptyVolumeInMemory(), - mount: volumeMount(name, m, false, ""), - }) - } - - for _, s := range s.Secrets { - name := fmt.Sprintf("%s-%s", project.Name, s.Source) - target := path.Join("/run/secrets", or(s.Target, path.Join(s.Source, s.Source))) - - specs = append(specs, secretMount(name, target)) - } - - for i, c := range s.Configs { - name := fmt.Sprintf("config-%d", i) - - target := or(c.Target, "/"+c.Source) - subPath := name - readOnly := true - - specs = append(specs, volumeSpec{ - source: configVolume(c, project.Configs[name], subPath), - mount: volumeMount(name, target, readOnly, subPath), - }) - } - - return specs, nil -} - -func or(v string, defaultValue string) string { - if v != "" && v != "." { - return v - } - - return defaultValue -} - -func toVolumeMounts(project *types.Project, s types.ServiceConfig) ([]apiv1.VolumeMount, error) { - var mounts []apiv1.VolumeMount - specs, err := toVolumeSpecs(project, s) - if err != nil { - return nil, err - } - for _, spec := range specs { - mounts = append(mounts, spec.mount) - } - return mounts, nil -} - -func toVolumes(project *types.Project, s types.ServiceConfig) ([]apiv1.Volume, error) { - var volumes []apiv1.Volume - specs, err := toVolumeSpecs(project, s) - if err != nil { - return nil, err - } - for _, spec := range specs { - if spec.source == nil { - spec.source = emptyVolumeInMemory() - } - volumes = append(volumes, apiv1.Volume{ - Name: spec.mount.Name, - VolumeSource: *spec.source, - }) - } - return volumes, nil -} - -func gitVolume(path string) *apiv1.VolumeSource { - return &apiv1.VolumeSource{ - GitRepo: &apiv1.GitRepoVolumeSource{ - Repository: filepath.ToSlash(path), - }, - } -} - -func hostPathVolume(path string) *apiv1.VolumeSource { - return &apiv1.VolumeSource{ - HostPath: &apiv1.HostPathVolumeSource{ - Path: path, - }, - } -} - -func defaultMode(mode *uint32) *int32 { - var defaultMode *int32 - - if mode != nil { - signedMode := int32(*mode) - defaultMode = &signedMode - } - - return defaultMode -} - -func secretMount(name, target string) volumeSpec { - readOnly := true - - filename := filepath.Base(target) - dir := filepath.Dir(target) - - return volumeSpec{ - source: &apiv1.VolumeSource{ - Secret: &apiv1.SecretVolumeSource{ - SecretName: name, - Items: []apiv1.KeyToPath{ - { - Key: name, - Path: filename, - }, - }, - }, - }, - mount: apiv1.VolumeMount{ - Name: filename, - MountPath: dir, - ReadOnly: readOnly, - }, - } -} - -func volumeMount(name, path string, readOnly bool, subPath string) apiv1.VolumeMount { - return apiv1.VolumeMount{ - Name: name, - MountPath: path, - ReadOnly: readOnly, - SubPath: subPath, - } -} - -func configVolume(config types.ServiceConfigObjConfig, topLevelConfig types.ConfigObjConfig, subPath string) *apiv1.VolumeSource { - return &apiv1.VolumeSource{ - ConfigMap: &apiv1.ConfigMapVolumeSource{ - LocalObjectReference: apiv1.LocalObjectReference{ - Name: config.Source, - }, - Items: []apiv1.KeyToPath{ - { - Key: toKey(topLevelConfig.File), - Path: subPath, - Mode: defaultMode(config.Mode), - }, - }, - }, - } -} - -func toKey(file string) string { - if file != "" { - return path.Base(file) - } - - return "file" // TODO: hard-coded key for external configs -} - -func emptyVolumeInMemory() *apiv1.VolumeSource { - return &apiv1.VolumeSource{ - EmptyDir: &apiv1.EmptyDirVolumeSource{ - Medium: apiv1.StorageMediumMemory, - }, - } -} diff --git a/local/backend.go b/local/backend.go deleted file mode 100644 index b92ee3493..000000000 --- a/local/backend.go +++ /dev/null @@ -1,96 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package local - -import ( - "os" - - "github.com/docker/cli/cli/command" - cliconfig "github.com/docker/cli/cli/config" - cliflags "github.com/docker/cli/cli/flags" - "github.com/docker/docker/client" - - "github.com/docker/compose-cli/api/backend" - "github.com/docker/compose-cli/api/containers" - "github.com/docker/compose-cli/api/resources" - "github.com/docker/compose-cli/api/secrets" - "github.com/docker/compose-cli/api/volumes" - cliopts "github.com/docker/compose-cli/cli/options" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/compose" -) - -type local struct { - containerService *containerService - volumeService *volumeService - composeService api.Service -} - -// NewService build a backend for "local" context, using Docker API client -func NewService(apiClient client.APIClient) backend.Service { - file := cliconfig.LoadDefaultConfigFile(os.Stderr) - return &local{ - containerService: &containerService{apiClient}, - volumeService: &volumeService{apiClient}, - composeService: compose.NewComposeService(apiClient, file), - } -} - -// GetLocalBackend initialize local backend -func GetLocalBackend(configDir string, opts cliopts.GlobalOpts) (backend.Service, error) { - configFile, err := cliconfig.Load(configDir) - if err != nil { - return nil, err - } - options := cliflags.CommonOptions{ - Context: opts.Context, - Debug: opts.Debug, - Hosts: opts.Hosts, - LogLevel: opts.LogLevel, - } - - if opts.TLSVerify { - options.TLS = opts.TLS - options.TLSVerify = opts.TLSVerify - options.TLSOptions = opts.TLSOptions - } - apiClient, err := command.NewAPIClientFromFlags(&options, configFile) - if err != nil { - return nil, err - } - return NewService(apiClient), nil -} - -func (s *local) ContainerService() containers.Service { - return s.containerService -} - -func (s *local) ComposeService() api.Service { - return s.composeService -} - -func (s *local) SecretsService() secrets.Service { - return nil -} - -func (s *local) VolumeService() volumes.Service { - return s.volumeService -} - -func (s *local) ResourceService() resources.Service { - return nil -} diff --git a/local/containers.go b/local/containers.go deleted file mode 100644 index 358b2f56e..000000000 --- a/local/containers.go +++ /dev/null @@ -1,277 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package local - -import ( - "bufio" - "context" - "io" - "strings" - "time" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/mount" - "github.com/docker/docker/api/types/network" - "github.com/docker/docker/client" - "github.com/docker/docker/pkg/stdcopy" - "github.com/docker/docker/pkg/stringid" - specs "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/pkg/errors" - - "github.com/docker/compose-cli/api/containers" - "github.com/docker/compose-cli/local/moby" - "github.com/docker/compose-cli/pkg/api" -) - -type containerService struct { - apiClient client.APIClient -} - -func (cs *containerService) Inspect(ctx context.Context, id string) (containers.Container, error) { - - c, err := cs.apiClient.ContainerInspect(ctx, id) - if err != nil { - return containers.Container{}, err - } - - status := "" - if c.State != nil { - status = c.State.Status - } - - command := "" - if c.Config != nil && - c.Config.Cmd != nil { - command = strings.Join(c.Config.Cmd, " ") - } - - rc := moby.ToRuntimeConfig(&c) - hc := moby.ToHostConfig(&c) - - return containers.Container{ - ID: stringid.TruncateID(c.ID), - Status: status, - Image: c.Image, - Command: command, - Platform: c.Platform, - Config: rc, - HostConfig: hc, - }, nil -} - -func (cs *containerService) List(ctx context.Context, all bool) ([]containers.Container, error) { - css, err := cs.apiClient.ContainerList(ctx, types.ContainerListOptions{ - All: all, - }) - - if err != nil { - return []containers.Container{}, err - } - - var result []containers.Container - for _, container := range css { - result = append(result, containers.Container{ - ID: stringid.TruncateID(container.ID), - Image: container.Image, - // TODO: `Status` is a human readable string ("Up 24 minutes"), - // we need to return the `State` instead but first we need to - // define an enum on the proto side with all the possible container - // statuses. We also need to add a `Created` property on the gRPC side. - Status: container.Status, - Command: container.Command, - Ports: moby.ToPorts(container.Ports), - }) - } - - return result, nil -} - -func (cs *containerService) Run(ctx context.Context, r containers.ContainerConfig) error { - exposedPorts, hostBindings, err := moby.FromPorts(r.Ports) - if err != nil { - return err - } - - var mounts []mount.Mount - for _, v := range r.Volumes { - tokens := strings.Split(v, ":") - if len(tokens) != 2 { - return errors.Wrapf(api.ErrParsingFailed, "volume %q has invalid format", v) - } - src := tokens[0] - tgt := tokens[1] - mounts = append(mounts, mount.Mount{Type: "volume", Source: src, Target: tgt}) - } - - containerConfig := &container.Config{ - Image: r.Image, - Cmd: r.Command, - Labels: r.Labels, - Env: r.Environment, - ExposedPorts: exposedPorts, - } - hostConfig := &container.HostConfig{ - PortBindings: hostBindings, - Mounts: mounts, - AutoRemove: r.AutoRemove, - RestartPolicy: moby.ToRestartPolicy(r.RestartPolicyCondition), - Resources: container.Resources{ - NanoCPUs: int64(r.CPULimit * 1e9), - Memory: int64(r.MemLimit), - }, - } - - id, err := cs.create(ctx, containerConfig, hostConfig, nil, r.Platform, r.ID) - if err != nil { - return err - } - return cs.apiClient.ContainerStart(ctx, id, types.ContainerStartOptions{}) -} - -func (cs *containerService) create(ctx context.Context, - containerConfig *container.Config, - hostConfig *container.HostConfig, - networkingConfig *network.NetworkingConfig, - platform *specs.Platform, name string) (string, error) { - created, err := cs.apiClient.ContainerCreate(ctx, containerConfig, hostConfig, networkingConfig, platform, name) - - if err != nil { - if client.IsErrNotFound(err) { - io, err := cs.apiClient.ImagePull(ctx, containerConfig.Image, types.ImagePullOptions{}) - if err != nil { - return "", err - } - scanner := bufio.NewScanner(io) - - // Read the whole body, otherwise the pulling stops - for scanner.Scan() { - } - - if err = scanner.Err(); err != nil { - return "", err - } - if err = io.Close(); err != nil { - return "", err - } - created, err = cs.apiClient.ContainerCreate(ctx, containerConfig, hostConfig, networkingConfig, platform, name) - if err != nil { - return "", err - } - } else { - return "", err - } - } - return created.ID, nil -} - -func (cs *containerService) Start(ctx context.Context, containerID string) error { - return cs.apiClient.ContainerStart(ctx, containerID, types.ContainerStartOptions{}) -} - -func (cs *containerService) Stop(ctx context.Context, containerID string, timeout *uint32) error { - var t *time.Duration - if timeout != nil { - timeoutValue := time.Duration(*timeout) * time.Second - t = &timeoutValue - } - return cs.apiClient.ContainerStop(ctx, containerID, t) -} - -func (cs *containerService) Kill(ctx context.Context, containerID string, signal string) error { - return cs.apiClient.ContainerKill(ctx, containerID, signal) -} - -func (cs *containerService) Exec(ctx context.Context, name string, request containers.ExecRequest) error { - cec, err := cs.apiClient.ContainerExecCreate(ctx, name, types.ExecConfig{ - Cmd: []string{request.Command}, - Tty: true, - AttachStdin: true, - AttachStdout: true, - AttachStderr: true, - }) - if err != nil { - return err - } - resp, err := cs.apiClient.ContainerExecAttach(ctx, cec.ID, types.ExecStartCheck{ - Tty: true, - }) - if err != nil { - return err - } - defer resp.Close() - - readChannel := make(chan error, 10) - writeChannel := make(chan error, 10) - - go func() { - _, err := io.Copy(request.Stdout, resp.Reader) - readChannel <- err - }() - - go func() { - _, err := io.Copy(resp.Conn, request.Stdin) - writeChannel <- err - }() - - for { - select { - case err := <-readChannel: - return err - case err := <-writeChannel: - return err - } - } -} - -func (cs *containerService) Logs(ctx context.Context, containerName string, request containers.LogsRequest) error { - c, err := cs.apiClient.ContainerInspect(ctx, containerName) - if err != nil { - return err - } - - r, err := cs.apiClient.ContainerLogs(ctx, containerName, types.ContainerLogsOptions{ - ShowStdout: true, - ShowStderr: true, - Follow: request.Follow, - }) - - if err != nil { - return err - } - - // nolint errcheck - defer r.Close() - - if c.Config.Tty { - _, err = io.Copy(request.Writer, r) - } else { - _, err = stdcopy.StdCopy(request.Writer, request.Writer, r) - } - - return err -} - -func (cs *containerService) Delete(ctx context.Context, containerID string, request containers.DeleteRequest) error { - err := cs.apiClient.ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{ - Force: request.Force, - }) - if client.IsErrNotFound(err) { - return errors.Wrapf(api.ErrNotFound, "container %q", containerID) - } - return err -} diff --git a/local/doc.go b/local/doc.go deleted file mode 100644 index 81aae393e..000000000 --- a/local/doc.go +++ /dev/null @@ -1,17 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package local diff --git a/local/e2e/cli-only/e2e_test.go b/local/e2e/cli-only/e2e_test.go deleted file mode 100644 index 896387e2b..000000000 --- a/local/e2e/cli-only/e2e_test.go +++ /dev/null @@ -1,540 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package main - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "runtime" - "strings" - "testing" - "time" - - "gotest.tools/v3/assert" - "gotest.tools/v3/golden" - "gotest.tools/v3/icmd" - - . "github.com/docker/compose-cli/utils/e2e" -) - -var binDir string - -func TestMain(m *testing.M) { - p, cleanup, err := SetupExistingCLI() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - binDir = p - exitCode := m.Run() - cleanup() - os.Exit(exitCode) -} - -func TestContextDefault(t *testing.T) { - c := NewParallelE2eCLI(t, binDir) - - t.Run("show", func(t *testing.T) { - res := c.RunDockerCmd("context", "show") - res.Assert(t, icmd.Expected{Out: "default"}) - }) - - t.Run("ls", func(t *testing.T) { - res := c.RunDockerCmd("context", "ls") - golden.Assert(t, res.Stdout(), GoldenFile("ls-out-default")) - - res = c.RunDockerCmd("context", "ls", "--format", "pretty") - golden.Assert(t, res.Stdout(), GoldenFile("ls-out-default")) - - res = c.RunDockerCmd("context", "ls", "--format", "json") - golden.Assert(t, res.Stdout(), GoldenFile("ls-out-json")) - - res = c.RunDockerCmd("context", "ls", "--format", "{{ json . }}") - golden.Assert(t, res.Stdout(), GoldenFile("ls-out-legacy-json")) - }) - - t.Run("inspect", func(t *testing.T) { - res := c.RunDockerCmd("context", "inspect", "default") - res.Assert(t, icmd.Expected{Out: `"Name": "default"`}) - }) - - t.Run("inspect current", func(t *testing.T) { - res := c.RunDockerCmd("context", "inspect") - res.Assert(t, icmd.Expected{Out: `"Name": "default"`}) - }) -} - -func TestContextCreateDocker(t *testing.T) { - c := NewParallelE2eCLI(t, binDir) - res := c.RunDockerCmd("context", "create", "test-docker", "--from", "default") - res.Assert(t, icmd.Expected{Out: "test-docker"}) - - t.Run("ls", func(t *testing.T) { - res := c.RunDockerCmd("context", "ls") - golden.Assert(t, res.Stdout(), GoldenFile("ls-out-test-docker")) - }) - - t.Run("ls quiet", func(t *testing.T) { - res := c.RunDockerCmd("context", "ls", "-q") - golden.Assert(t, res.Stdout(), "ls-out-test-docker-quiet.golden") - }) - - t.Run("ls format", func(t *testing.T) { - res := c.RunDockerCmd("context", "ls", "--format", "{{ json . }}") - res.Assert(t, icmd.Expected{Out: `"Name":"default"`}) - }) -} - -func TestContextInspect(t *testing.T) { - c := NewParallelE2eCLI(t, binDir) - res := c.RunDockerCmd("context", "create", "test-docker", "--from", "default") - res.Assert(t, icmd.Expected{Out: "test-docker"}) - - t.Run("inspect current", func(t *testing.T) { - // Cannot be run in parallel because of "context use" - res := c.RunDockerCmd("context", "use", "test-docker") - res.Assert(t, icmd.Expected{Out: "test-docker"}) - - res = c.RunDockerCmd("context", "inspect") - res.Assert(t, icmd.Expected{Out: `"Name": "test-docker"`}) - }) -} - -func TestContextHelpACI(t *testing.T) { - c := NewParallelE2eCLI(t, binDir) - - t.Run("help", func(t *testing.T) { - res := c.RunDockerCmd("context", "create", "aci", "--help") - // Can't use golden here as the help prints the config directory which changes - res.Assert(t, icmd.Expected{Out: "docker context create aci CONTEXT [flags]"}) - res.Assert(t, icmd.Expected{Out: "--location"}) - res.Assert(t, icmd.Expected{Out: "--subscription-id"}) - res.Assert(t, icmd.Expected{Out: "--resource-group"}) - }) - - t.Run("check exec", func(t *testing.T) { - res := c.RunDockerOrExitError("context", "create", "aci", "--subscription-id", "invalid-id") - res.Assert(t, icmd.Expected{ - ExitCode: 1, - Err: "accepts 1 arg(s), received 0", - }) - assert.Assert(t, !strings.Contains(res.Combined(), "unknown flag")) - }) -} - -func TestContextMetrics(t *testing.T) { - c := NewParallelE2eCLI(t, binDir) - s := NewMetricsServer(c.MetricsSocket()) - s.Start() - defer s.Stop() - - started := false - for i := 0; i < 30; i++ { - c.RunDockerCmd("help", "ps") - if len(s.GetUsage()) > 0 { - started = true - fmt.Printf(" [%s] Server up in %d ms\n", t.Name(), i*100) - break - } - time.Sleep(100 * time.Millisecond) - } - assert.Assert(t, started, "Metrics mock server not available after 3 secs") - - t.Run("send metrics on help commands", func(t *testing.T) { - s.ResetUsage() - - c.RunDockerCmd("help", "run") - c.RunDockerCmd("--help") - c.RunDockerCmd("run", "--help") - - usage := s.GetUsage() - assert.DeepEqual(t, []string{ - `{"command":"help run","context":"moby","source":"cli","status":"success"}`, - `{"command":"--help","context":"moby","source":"cli","status":"success"}`, - `{"command":"--help run","context":"moby","source":"cli","status":"success"}`, - }, usage) - }) - - t.Run("metrics on default context", func(t *testing.T) { - s.ResetUsage() - - c.RunDockerCmd("ps") - c.RunDockerCmd("version") - c.RunDockerOrExitError("version", "--xxx") - - usage := s.GetUsage() - assert.DeepEqual(t, []string{ - `{"command":"ps","context":"moby","source":"cli","status":"success"}`, - `{"command":"version","context":"moby","source":"cli","status":"success"}`, - `{"command":"version","context":"moby","source":"cli","status":"failure-cmd-syntax"}`, - }, usage) - }) - - t.Run("metrics on other context type", func(t *testing.T) { - s.ResetUsage() - - c.RunDockerCmd("context", "create", "local", "test-local") - c.RunDockerCmd("ps") - c.RunDockerCmd("context", "use", "test-local") - c.RunDockerCmd("ps") - c.RunDockerOrExitError("stop", "unknown") - c.RunDockerCmd("context", "use", "default") - c.RunDockerCmd("--context", "test-local", "ps") - c.RunDockerCmd("context", "ls") - - usage := s.GetUsage() - assert.DeepEqual(t, []string{ - `{"command":"context create","context":"moby","source":"cli","status":"success"}`, - `{"command":"ps","context":"moby","source":"cli","status":"success"}`, - `{"command":"context use","context":"moby","source":"cli","status":"success"}`, - `{"command":"ps","context":"local","source":"cli","status":"success"}`, - `{"command":"stop","context":"local","source":"cli","status":"failure"}`, - `{"command":"context use","context":"local","source":"cli","status":"success"}`, - `{"command":"ps","context":"local","source":"cli","status":"success"}`, - `{"command":"context ls","context":"moby","source":"cli","status":"success"}`, - }, usage) - }) -} - -func TestContextDuplicateACI(t *testing.T) { - c := NewParallelE2eCLI(t, binDir) - - c.RunDockerCmd("context", "create", "mycontext", "--from", "default") - res := c.RunDockerOrExitError("context", "create", "aci", "mycontext") - res.Assert(t, icmd.Expected{ - ExitCode: 1, - Err: "context mycontext: already exists", - }) -} - -func TestContextRemove(t *testing.T) { - t.Run("remove current", func(t *testing.T) { - c := NewParallelE2eCLI(t, binDir) - - c.RunDockerCmd("context", "create", "test-context-rm", "--from", "default") - res := c.RunDockerCmd("context", "use", "test-context-rm") - res.Assert(t, icmd.Expected{Out: "test-context-rm"}) - res = c.RunDockerOrExitError("context", "rm", "test-context-rm") - res.Assert(t, icmd.Expected{ - ExitCode: 1, - Err: "cannot delete current context", - }) - }) - - t.Run("force remove current", func(t *testing.T) { - c := NewParallelE2eCLI(t, binDir) - - c.RunDockerCmd("context", "create", "test-context-rmf") - c.RunDockerCmd("context", "use", "test-context-rmf") - res := c.RunDockerCmd("context", "rm", "-f", "test-context-rmf") - res.Assert(t, icmd.Expected{Out: "test-context-rmf"}) - res = c.RunDockerCmd("context", "ls") - res.Assert(t, icmd.Expected{Out: "default *"}) - }) -} - -func TestLoginCommandDelegation(t *testing.T) { - // These tests just check that the existing CLI is called in various cases. - // They do not test actual login functionality. - c := NewParallelE2eCLI(t, binDir) - - t.Run("default context", func(t *testing.T) { - res := c.RunDockerOrExitError("login", "-u", "nouser", "-p", "wrongpasword") - res.Assert(t, icmd.Expected{ - ExitCode: 1, - Err: "unauthorized: incorrect username or password", - }) - }) - - t.Run("interactive", func(t *testing.T) { - res := c.RunDockerOrExitError("login", "someregistry.docker.io") - res.Assert(t, icmd.Expected{ - ExitCode: 1, - Err: "Cannot perform an interactive login from a non TTY device", - }) - }) - - t.Run("localhost registry interactive", func(t *testing.T) { - res := c.RunDockerOrExitError("login", "localhost:443") - res.Assert(t, icmd.Expected{ - ExitCode: 1, - Err: "Cannot perform an interactive login from a non TTY device", - }) - }) - - t.Run("localhost registry", func(t *testing.T) { - res := c.RunDockerOrExitError("login", "localhost", "-u", "user", "-p", "password") - res.Assert(t, icmd.Expected{ - ExitCode: 1, - Err: "http://localhost/v2/", - }) - }) - - t.Run("logout", func(t *testing.T) { - res := c.RunDockerCmd("logout", "someregistry.docker.io") - res.Assert(t, icmd.Expected{Out: "Removing login credentials for someregistry.docker.io"}) - }) - - t.Run("logout", func(t *testing.T) { - res := c.RunDockerCmd("logout", "localhost:443") - res.Assert(t, icmd.Expected{Out: "Removing login credentials for localhost:443"}) - }) - - t.Run("existing context", func(t *testing.T) { - c.RunDockerCmd("context", "create", "local", "local") - c.RunDockerCmd("context", "use", "local") - res := c.RunDockerOrExitError("login", "-u", "nouser", "-p", "wrongpasword") - res.Assert(t, icmd.Expected{ - ExitCode: 1, - Err: "unauthorized: incorrect username or password", - }) - }) -} - -func TestMissingExistingCLI(t *testing.T) { - t.Parallel() - home, err := ioutil.TempDir("", "") - assert.NilError(t, err) - t.Cleanup(func() { - _ = os.RemoveAll(home) - }) - - bin, err := ioutil.TempDir("", "") - assert.NilError(t, err) - t.Cleanup(func() { - _ = os.RemoveAll(bin) - }) - err = CopyFile(filepath.Join(binDir, DockerExecutableName), filepath.Join(bin, DockerExecutableName)) - assert.NilError(t, err) - - env := []string{"PATH=" + bin} - if runtime.GOOS == "windows" { - env = append(env, "USERPROFILE="+home) - - } else { - env = append(env, "HOME="+home) - } - - c := icmd.Cmd{ - Env: env, - Command: []string{filepath.Join(bin, "docker")}, - } - res := icmd.RunCmd(c) - res.Assert(t, icmd.Expected{ - ExitCode: 1, - Err: `"com.docker.cli": executable file not found`, - }) -} - -func TestLegacy(t *testing.T) { - c := NewParallelE2eCLI(t, binDir) - - t.Run("help", func(t *testing.T) { - res := c.RunDockerCmd("--help") - res.Assert(t, icmd.Expected{Out: "swarm"}) - }) - - t.Run("swarm", func(t *testing.T) { - res := c.RunDockerOrExitError("swarm", "join") - res.Assert(t, icmd.Expected{ - ExitCode: 1, - Err: `"docker swarm join" requires exactly 1 argument.`, - }) - }) - - t.Run("local run", func(t *testing.T) { - cmd := c.NewDockerCmd("run", "--rm", "hello-world") - cmd.Timeout = 40 * time.Second - res := icmd.RunCmd(cmd) - res.Assert(t, icmd.Expected{Out: "Hello from Docker!"}) - }) - - t.Run("error messages", func(t *testing.T) { - res := c.RunDockerOrExitError("foo") - res.Assert(t, icmd.Expected{ - ExitCode: 1, - Err: "docker: 'foo' is not a docker command.", - }) - }) - - t.Run("run without HOME defined", func(t *testing.T) { - cmd := c.NewDockerCmd("ps") - cmd.Env = []string{"PATH=" + c.PathEnvVar()} - res := icmd.RunCmd(cmd) - res.Assert(t, icmd.Expected{ - ExitCode: 0, - Out: "CONTAINER ID", - }) - assert.Equal(t, res.Stderr(), "") - }) - - t.Run("run without write access to context store", func(t *testing.T) { - cmd := c.NewDockerCmd("ps") - cmd.Env = []string{"PATH=" + c.PathEnvVar(), "HOME=/doesnotexist/"} - res := icmd.RunCmd(cmd) - res.Assert(t, icmd.Expected{ - ExitCode: 0, - Out: "CONTAINER ID", - }) - }) - - t.Run("host flag", func(t *testing.T) { - res := c.RunDockerOrExitError("-H", "tcp://nonexistent:123", "version") - assert.Assert(t, res.ExitCode == 1) - assert.Assert(t, strings.Contains(res.Stderr(), "dial tcp: lookup nonexistent"), res.Stderr()) - }) - - t.Run("remote engine context", func(t *testing.T) { - c.RunDockerCmd("context", "create", "test-remote-engine", "--docker", "host=tcp://nonexistent:1234") - c.RunDockerCmd("context", "use", "test-remote-engine") - - res := c.RunDockerOrExitError("version") - assert.Assert(t, res.ExitCode == 1) - assert.Assert(t, strings.Contains(res.Stderr(), "dial tcp: lookup nonexistent"), res.Stderr()) - }) - - t.Run("existing contexts delegate", func(t *testing.T) { - c.RunDockerCmd("context", "create", "moby-ctx", "--from=default") - c.RunDockerCmd("context", "use", "moby-ctx") - res := c.RunDockerOrExitError("swarm", "join") - res.Assert(t, icmd.Expected{ - ExitCode: 1, - Err: `"docker swarm join" requires exactly 1 argument.`, - }) - res = c.RunDockerCmd("context", "update", "moby-ctx", "--description", "updated context") - res.Assert(t, icmd.Expected{Out: "moby-ctx"}) - }) - - t.Run("host flag overrides context", func(t *testing.T) { - c.RunDockerCmd("context", "create", "local", "test-local") - c.RunDockerCmd("context", "use", "test-local") - endpoint := "unix:///var/run/docker.sock" - if runtime.GOOS == "windows" { - endpoint = "npipe:////./pipe/docker_engine" - } - res := c.RunDockerCmd("-H", endpoint, "images") - // Local backend does not have images command - assert.Assert(t, strings.Contains(res.Stdout(), "IMAGE ID"), res.Stdout()) - }) -} - -func TestLegacyLogin(t *testing.T) { - c := NewParallelE2eCLI(t, binDir) - - t.Run("host flag login", func(t *testing.T) { - res := c.RunDockerOrExitError("-H", "tcp://localhost:123", "login", "-u", "nouser", "-p", "wrongpasword") - res.Assert(t, icmd.Expected{ - ExitCode: 1, - Err: "WARNING! Using --password via the CLI is insecure. Use --password-stdin.", - }) - }) - - t.Run("log level flag login", func(t *testing.T) { - res := c.RunDockerOrExitError("--log-level", "debug", "login", "-u", "nouser", "-p", "wrongpasword") - res.Assert(t, icmd.Expected{ - ExitCode: 1, - Err: "WARNING! Using --password via the CLI is insecure", - }) - }) -} - -func TestUnsupportedCommand(t *testing.T) { - c := NewParallelE2eCLI(t, binDir) - - c.RunDockerCmd("context", "create", "local", "test-local") - res := c.RunDockerOrExitError("--context", "test-local", "images") - res.Assert(t, icmd.Expected{ - ExitCode: 1, - Err: `Command "images" not available in current context (test-local), you can use the "default" context to run this command`, - }) -} - -func TestBackendMetadata(t *testing.T) { - c := NewParallelE2eCLI(t, binDir) - - t.Run("backend-metadata", func(t *testing.T) { - res := c.RunDockerCmd("backend-metadata") - res.Assert(t, icmd.Expected{Out: `{"Name":"Cloud integration","Version":"`}) - }) -} - -func TestVersion(t *testing.T) { - c := NewParallelE2eCLI(t, binDir) - - t.Run("azure version", func(t *testing.T) { - res := c.RunDockerCmd("version") - res.Assert(t, icmd.Expected{Out: "Cloud integration"}) - }) - - t.Run("format", func(t *testing.T) { - res := c.RunDockerCmd("version", "-f", "{{ json . }}") - res.Assert(t, icmd.Expected{Out: `"Client":`}) - res = c.RunDockerCmd("version", "--format", "{{ json . }}") - res.Assert(t, icmd.Expected{Out: `"Client":`}) - }) - - t.Run("format legacy", func(t *testing.T) { - res := c.RunDockerCmd("version", "-f", "{{ json .Client }}") - res.Assert(t, icmd.Expected{Out: `"DefaultAPIVersion":`}) - res = c.RunDockerCmd("version", "--format", "{{ json .Server }}") - res.Assert(t, icmd.Expected{Out: `"KernelVersion":`}) - }) - - t.Run("format cloud integration", func(t *testing.T) { - res := c.RunDockerCmd("version", "-f", "pretty") - res.Assert(t, icmd.Expected{Out: `Cloud integration:`}) - res = c.RunDockerCmd("version", "-f", "") - res.Assert(t, icmd.Expected{Out: `Cloud integration:`}) - - res = c.RunDockerCmd("version", "-f", "json") - res.Assert(t, icmd.Expected{Out: `"CloudIntegration":`}) - res = c.RunDockerCmd("version", "-f", "{{ json . }}") - res.Assert(t, icmd.Expected{Out: `"CloudIntegration":`}) - res = c.RunDockerCmd("version", "--format", "{{json .}}") - res.Assert(t, icmd.Expected{Out: `"CloudIntegration":`}) - res = c.RunDockerCmd("version", "--format", "{{json . }}") - res.Assert(t, icmd.Expected{Out: `"CloudIntegration":`}) - res = c.RunDockerCmd("version", "--format", "{{ json .}}") - res.Assert(t, icmd.Expected{Out: `"CloudIntegration":`}) - res = c.RunDockerCmd("version", "--format", "{{ json . }}") - res.Assert(t, icmd.Expected{Out: `"CloudIntegration":`}) - }) - - t.Run("delegate version flag", func(t *testing.T) { - c.RunDockerCmd("context", "create", "local", "test-local") - c.RunDockerCmd("context", "use", "test-local") - res := c.RunDockerCmd("-v") - res.Assert(t, icmd.Expected{Out: "Docker version"}) - }) -} - -func TestFailOnEcsUsageAsPlugin(t *testing.T) { - c := NewParallelE2eCLI(t, binDir) - res := c.RunDockerCmd("context", "create", "local", "local") - res.Assert(t, icmd.Expected{}) - - t.Run("fail on ecs usage as plugin", func(t *testing.T) { - res := c.RunDockerOrExitError("--context", "local", "ecs", "compose", "up") - res.Assert(t, icmd.Expected{ - ExitCode: 1, - Out: "", - Err: "The ECS integration is now part of the CLI. Use `docker compose` with an ECS context.", - }) - }) -} diff --git a/local/e2e/cli-only/testdata/ls-out-default-windows.golden b/local/e2e/cli-only/testdata/ls-out-default-windows.golden deleted file mode 100644 index c01e06593..000000000 --- a/local/e2e/cli-only/testdata/ls-out-default-windows.golden +++ /dev/null @@ -1,2 +0,0 @@ -NAME TYPE DESCRIPTION DOCKER ENDPOINT KUBERNETES ENDPOINT ORCHESTRATOR -default * moby Current DOCKER_HOST based configuration npipe:////./pipe/docker_engine swarm diff --git a/local/e2e/cli-only/testdata/ls-out-default.golden b/local/e2e/cli-only/testdata/ls-out-default.golden deleted file mode 100644 index 838fc0036..000000000 --- a/local/e2e/cli-only/testdata/ls-out-default.golden +++ /dev/null @@ -1,2 +0,0 @@ -NAME TYPE DESCRIPTION DOCKER ENDPOINT KUBERNETES ENDPOINT ORCHESTRATOR -default * moby Current DOCKER_HOST based configuration unix:///var/run/docker.sock swarm diff --git a/local/e2e/cli-only/testdata/ls-out-json-windows.golden b/local/e2e/cli-only/testdata/ls-out-json-windows.golden deleted file mode 100644 index 4d2c3306e..000000000 --- a/local/e2e/cli-only/testdata/ls-out-json-windows.golden +++ /dev/null @@ -1 +0,0 @@ -[{"Current":true,"Description":"Current DOCKER_HOST based configuration","DockerEndpoint":"npipe:////./pipe/docker_engine","KubernetesEndpoint":"","ContextType":"moby","Name":"default","StackOrchestrator":"swarm"}] diff --git a/local/e2e/cli-only/testdata/ls-out-json.golden b/local/e2e/cli-only/testdata/ls-out-json.golden deleted file mode 100644 index c387b2cde..000000000 --- a/local/e2e/cli-only/testdata/ls-out-json.golden +++ /dev/null @@ -1 +0,0 @@ -[{"Current":true,"Description":"Current DOCKER_HOST based configuration","DockerEndpoint":"unix:///var/run/docker.sock","KubernetesEndpoint":"","ContextType":"moby","Name":"default","StackOrchestrator":"swarm"}] diff --git a/local/e2e/cli-only/testdata/ls-out-legacy-json-windows.golden b/local/e2e/cli-only/testdata/ls-out-legacy-json-windows.golden deleted file mode 100644 index 8417af140..000000000 --- a/local/e2e/cli-only/testdata/ls-out-legacy-json-windows.golden +++ /dev/null @@ -1 +0,0 @@ -{"Current":true,"Description":"Current DOCKER_HOST based configuration","DockerEndpoint":"npipe:////./pipe/docker_engine","KubernetesEndpoint":"","ContextType":"moby","Name":"default","StackOrchestrator":"swarm"} diff --git a/local/e2e/cli-only/testdata/ls-out-legacy-json.golden b/local/e2e/cli-only/testdata/ls-out-legacy-json.golden deleted file mode 100644 index dadb250bb..000000000 --- a/local/e2e/cli-only/testdata/ls-out-legacy-json.golden +++ /dev/null @@ -1 +0,0 @@ -{"Current":true,"Description":"Current DOCKER_HOST based configuration","DockerEndpoint":"unix:///var/run/docker.sock","KubernetesEndpoint":"","ContextType":"moby","Name":"default","StackOrchestrator":"swarm"} diff --git a/local/e2e/cli-only/testdata/ls-out-test-docker-quiet.golden b/local/e2e/cli-only/testdata/ls-out-test-docker-quiet.golden deleted file mode 100644 index e29068480..000000000 --- a/local/e2e/cli-only/testdata/ls-out-test-docker-quiet.golden +++ /dev/null @@ -1,2 +0,0 @@ -default -test-docker diff --git a/local/e2e/cli-only/testdata/ls-out-test-docker-windows.golden b/local/e2e/cli-only/testdata/ls-out-test-docker-windows.golden deleted file mode 100644 index 5074d4126..000000000 --- a/local/e2e/cli-only/testdata/ls-out-test-docker-windows.golden +++ /dev/null @@ -1,3 +0,0 @@ -NAME TYPE DESCRIPTION DOCKER ENDPOINT KUBERNETES ENDPOINT ORCHESTRATOR -default * moby Current DOCKER_HOST based configuration npipe:////./pipe/docker_engine swarm -test-docker moby npipe:////./pipe/docker_engine swarm diff --git a/local/e2e/cli-only/testdata/ls-out-test-docker.golden b/local/e2e/cli-only/testdata/ls-out-test-docker.golden deleted file mode 100644 index b0c8e97a4..000000000 --- a/local/e2e/cli-only/testdata/ls-out-test-docker.golden +++ /dev/null @@ -1,3 +0,0 @@ -NAME TYPE DESCRIPTION DOCKER ENDPOINT KUBERNETES ENDPOINT ORCHESTRATOR -default * moby Current DOCKER_HOST based configuration unix:///var/run/docker.sock swarm -test-docker moby unix:///var/run/docker.sock swarm diff --git a/local/e2e/container/container_test.go b/local/e2e/container/container_test.go deleted file mode 100644 index ff4582ae8..000000000 --- a/local/e2e/container/container_test.go +++ /dev/null @@ -1,162 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package e2e - -import ( - "encoding/json" - "fmt" - "os" - "strings" - "testing" - "time" - - "gotest.tools/v3/assert" - "gotest.tools/v3/icmd" - "gotest.tools/v3/poll" - - "github.com/docker/compose-cli/cli/cmd" - . "github.com/docker/compose-cli/utils/e2e" -) - -var binDir string - -func TestMain(m *testing.M) { - p, cleanup, err := SetupExistingCLI() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - binDir = p - exitCode := m.Run() - cleanup() - os.Exit(exitCode) -} - -func TestLocalBackendRun(t *testing.T) { - c := NewParallelE2eCLI(t, binDir) - c.RunDockerCmd("context", "create", "local", "test-context").Assert(t, icmd.Success) - c.RunDockerCmd("context", "use", "test-context").Assert(t, icmd.Success) - - t.Run("run", func(t *testing.T) { - t.Parallel() - res := c.RunDockerCmd("run", "-d", "nginx:alpine") - containerName := strings.TrimSpace(res.Combined()) - t.Cleanup(func() { - _ = c.RunDockerOrExitError("rm", "-f", containerName) - }) - res = c.RunDockerCmd("inspect", containerName) - res.Assert(t, icmd.Expected{Out: `"Status": "running"`}) - }) - - t.Run("run rm", func(t *testing.T) { - t.Parallel() - res := c.RunDockerCmd("run", "--rm", "-d", "nginx:alpine") - containerName := strings.TrimSpace(res.Combined()) - t.Cleanup(func() { - _ = c.RunDockerOrExitError("rm", "-f", containerName) - }) - _ = c.RunDockerCmd("stop", containerName) - checkRemoved := func(t poll.LogT) poll.Result { - res = c.RunDockerOrExitError("inspect", containerName) - if res.ExitCode == 1 && strings.Contains(res.Stderr(), "No such container") { - return poll.Success() - } - return poll.Continue("waiting for container to be removed") - } - poll.WaitOn(t, checkRemoved, poll.WithDelay(1*time.Second), poll.WithTimeout(10*time.Second)) - }) - - t.Run("run with ports", func(t *testing.T) { - res := c.RunDockerCmd("run", "-d", "-p", "85:80", "nginx:alpine") - containerName := strings.TrimSpace(res.Combined()) - t.Cleanup(func() { - _ = c.RunDockerOrExitError("rm", "-f", containerName) - }) - res = c.RunDockerCmd("inspect", containerName) - - inspect := &cmd.ContainerInspectView{} - err := json.Unmarshal([]byte(res.Stdout()), inspect) - assert.NilError(t, err) - assert.Equal(t, inspect.Status, "running") - nginxID := inspect.ID - - res = c.RunDockerCmd("ps") - nginxFound := false - lines := Lines(res.Stdout()) - for _, line := range lines { - fields := strings.Fields(line) - if fields[0] == nginxID { - nginxFound = true - assert.Equal(t, fields[1], "nginx:alpine", res.Combined()) - assert.Equal(t, fields[2], "/docker-entrypoint.sh", res.Combined()) - assert.Assert(t, strings.Contains(fields[len(fields)-1], ":85->80/tcp"), res.Combined()) - } - } - assert.Assert(t, nginxFound, res.Stdout()) - - res = c.RunDockerCmd("ps", "--format", "json") - res.Assert(t, icmd.Expected{Out: `"Image":"nginx:alpine","Status":"Up Less than a second","Command":"/docker-entrypoint.sh nginx -g 'daemon off;'","Ports":["0.0.0.0:85->80/tcp"`}) - - res = c.RunDockerCmd("ps", "--quiet") - res.Assert(t, icmd.Expected{Out: nginxID + "\n"}) - }) - - t.Run("run with volume", func(t *testing.T) { - t.Parallel() - t.Cleanup(func() { - _ = c.RunDockerOrExitError("volume", "rm", "local-test") - }) - c.RunDockerCmd("volume", "create", "local-test") - c.RunDockerCmd("run", "--rm", "-d", "--volume", "local-test:/data", "alpine", "sh", "-c", `echo "testdata" > /data/test`) - // FIXME: Remove sleep when race to attach to dead container is fixed - res := c.RunDockerOrExitError("run", "--rm", "--volume", "local-test:/data", "alpine", "sh", "-c", "cat /data/test && sleep 1") - res.Assert(t, icmd.Expected{Out: "testdata"}) - }) - - t.Run("inspect not found", func(t *testing.T) { - t.Parallel() - res := c.RunDockerOrExitError("inspect", "nonexistentcontainer") - res.Assert(t, icmd.Expected{ - ExitCode: 1, - Err: "Error: No such container: nonexistentcontainer", - }) - }) -} - -func TestLocalBackendVolumes(t *testing.T) { - c := NewParallelE2eCLI(t, binDir) - c.RunDockerCmd("context", "create", "local", "test-context").Assert(t, icmd.Success) - c.RunDockerCmd("context", "use", "test-context").Assert(t, icmd.Success) - - t.Run("volume crud", func(t *testing.T) { - t.Parallel() - name := "crud" - t.Cleanup(func() { - _ = c.RunDockerOrExitError("volume", "rm", name) - }) - res := c.RunDockerCmd("volume", "create", name) - res.Assert(t, icmd.Expected{Out: name}) - res = c.RunDockerCmd("volume", "ls") - res.Assert(t, icmd.Expected{Out: name}) - res = c.RunDockerCmd("volume", "inspect", name) - res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`"ID": "%s"`, name)}) - res = c.RunDockerCmd("volume", "rm", name) - res.Assert(t, icmd.Expected{Out: name}) - res = c.RunDockerOrExitError("volume", "inspect", name) - res.Assert(t, icmd.Expected{ExitCode: 1}) - }) -} diff --git a/local/e2e/container/skip_win_ci_test.go b/local/e2e/container/skip_win_ci_test.go deleted file mode 100644 index 96b8f45c8..000000000 --- a/local/e2e/container/skip_win_ci_test.go +++ /dev/null @@ -1,83 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package e2e - -import ( - "io/ioutil" - "os" - "path/filepath" - "runtime" - "strings" - "syscall" - "testing" - "time" - - "gotest.tools/v3/assert" - "gotest.tools/v3/icmd" - "gotest.tools/v3/poll" - - . "github.com/docker/compose-cli/utils/e2e" -) - -func TestKillChildProcess(t *testing.T) { - assert.Assert(t, runtime.GOOS != "windows", "cannot test process signals on windows") - c := NewParallelE2eCLI(t, binDir) - - image := "test-sleep-image" - psCmd := icmd.Command("ps", "-x") - psRes := icmd.RunCmd(psCmd) - psRes.Assert(t, icmd.Success) - assert.Assert(t, !strings.Contains(psRes.Combined(), image)) - - d := writeDockerfile(t) - buildArgs := []string{"build", "--no-cache", "-t", image, "."} - cmd := c.NewDockerCmd(buildArgs...) - cmd.Dir = d - res := icmd.StartCmd(cmd) - - buildRunning := func(t poll.LogT) poll.Result { - res := icmd.RunCmd(psCmd) - if strings.Contains(res.Combined(), strings.Join(buildArgs, " ")) { - return poll.Success() - } - return poll.Continue("waiting for child process to be running") - } - poll.WaitOn(t, buildRunning, poll.WithDelay(1*time.Second)) - - err := res.Cmd.Process.Signal(syscall.SIGTERM) - assert.NilError(t, err) - buildStopped := func(t poll.LogT) poll.Result { - res := icmd.RunCmd(psCmd) - if !strings.Contains(res.Combined(), strings.Join(buildArgs, " ")) { - return poll.Success() - } - return poll.Continue("waiting for child process to be killed") - } - poll.WaitOn(t, buildStopped, poll.WithDelay(1*time.Second), poll.WithTimeout(60*time.Second)) -} - -func writeDockerfile(t *testing.T) string { - d, err := ioutil.TempDir("", "") - assert.NilError(t, err) - t.Cleanup(func() { - _ = os.RemoveAll(d) - }) - err = ioutil.WriteFile(filepath.Join(d, "Dockerfile"), []byte(`FROM alpine:3.10 -RUN sleep 100`), 0644) - assert.NilError(t, err) - return d -} diff --git a/local/e2e/container/testdata/ls-out-test-local.golden b/local/e2e/container/testdata/ls-out-test-local.golden deleted file mode 100644 index bfab12191..000000000 --- a/local/e2e/container/testdata/ls-out-test-local.golden +++ /dev/null @@ -1,3 +0,0 @@ -NAME TYPE DESCRIPTION DOCKER ENDPOINT KUBERNETES ENDPOINT ORCHESTRATOR -default moby Current DOCKER_HOST based configuration unix:///var/run/docker.sock swarm -test-local * local diff --git a/local/moby/convert.go b/local/moby/convert.go deleted file mode 100644 index b41180957..000000000 --- a/local/moby/convert.go +++ /dev/null @@ -1,153 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package moby - -import ( - "fmt" - "sort" - "strconv" - "strings" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/go-connections/nat" - - "github.com/docker/compose-cli/api/containers" -) - -// ToRuntimeConfig convert into containers.RuntimeConfig -func ToRuntimeConfig(m *types.ContainerJSON) *containers.RuntimeConfig { - if m.Config == nil { - return nil - } - var env map[string]string - if m.Config.Env != nil { - env = make(map[string]string) - for _, e := range m.Config.Env { - tokens := strings.Split(e, "=") - if len(tokens) != 2 { - continue - } - env[tokens[0]] = tokens[1] - } - } - - var labels []string - if m.Config.Labels != nil { - for k, v := range m.Config.Labels { - labels = append(labels, fmt.Sprintf("%s=%s", k, v)) - } - } - sort.Strings(labels) - - if env == nil && - labels == nil { - return nil - } - - return &containers.RuntimeConfig{ - Env: env, - Labels: labels, - } -} - -// ToHostConfig convert into containers.HostConfig -func ToHostConfig(m *types.ContainerJSON) *containers.HostConfig { - if m.HostConfig == nil { - return nil - } - - return &containers.HostConfig{ - AutoRemove: m.HostConfig.AutoRemove, - RestartPolicy: fromRestartPolicyName(m.HostConfig.RestartPolicy.Name), - CPULimit: float64(m.HostConfig.Resources.NanoCPUs) / 1e9, - MemoryLimit: uint64(m.HostConfig.Resources.Memory), - } -} - -// ToPorts convert into containers.Port -func ToPorts(ports []types.Port) []containers.Port { - result := []containers.Port{} - for _, port := range ports { - result = append(result, containers.Port{ - ContainerPort: uint32(port.PrivatePort), - HostPort: uint32(port.PublicPort), - HostIP: port.IP, - Protocol: port.Type, - }) - } - - return result -} - -// FromPorts convert to nat.Port / nat.PortBinding -func FromPorts(ports []containers.Port) (map[nat.Port]struct{}, map[nat.Port][]nat.PortBinding, error) { - var ( - exposedPorts = make(map[nat.Port]struct{}, len(ports)) - bindings = make(map[nat.Port][]nat.PortBinding) - ) - - for _, port := range ports { - p, err := nat.NewPort(port.Protocol, strconv.Itoa(int(port.ContainerPort))) - if err != nil { - return nil, nil, err - } - - if _, exists := exposedPorts[p]; !exists { - exposedPorts[p] = struct{}{} - } - - portBinding := nat.PortBinding{ - HostIP: port.HostIP, - HostPort: strconv.Itoa(int(port.HostPort)), - } - bslice, exists := bindings[p] - if !exists { - bslice = []nat.PortBinding{} - } - bindings[p] = append(bslice, portBinding) - } - - return exposedPorts, bindings, nil -} - -func fromRestartPolicyName(m string) string { - switch m { - case "always": - return containers.RestartPolicyAny - case "on-failure": - return containers.RestartPolicyOnFailure - case "no", "": - fallthrough - default: - return containers.RestartPolicyNone - } -} - -// ToRestartPolicy convert to container.RestartPolicy -func ToRestartPolicy(p string) container.RestartPolicy { - switch p { - case containers.RestartPolicyAny: - return container.RestartPolicy{Name: "always"} - case containers.RestartPolicyOnFailure: - return container.RestartPolicy{Name: "on-failure"} - case containers.RestartPolicyNone: - fallthrough - default: - return container.RestartPolicy{Name: "no"} - } -} diff --git a/local/moby/convert_test.go b/local/moby/convert_test.go deleted file mode 100644 index a9e5a4135..000000000 --- a/local/moby/convert_test.go +++ /dev/null @@ -1,101 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package moby - -import ( - "testing" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "gotest.tools/v3/assert" - - "github.com/docker/compose-cli/api/containers" -) - -func TestToRuntimeConfig(t *testing.T) { - t.Parallel() - m := &types.ContainerJSON{ - Config: &container.Config{ - Env: []string{"FOO1=BAR1", "FOO2=BAR2"}, - Labels: map[string]string{"foo1": "bar1", "foo2": "bar2"}, - }, - } - rc := ToRuntimeConfig(m) - res := &containers.RuntimeConfig{ - Env: map[string]string{"FOO1": "BAR1", "FOO2": "BAR2"}, - Labels: []string{"foo1=bar1", "foo2=bar2"}, - } - assert.DeepEqual(t, rc, res) -} - -func TestToHostConfig(t *testing.T) { - t.Parallel() - base := &types.ContainerJSONBase{ - HostConfig: &container.HostConfig{ - AutoRemove: true, - RestartPolicy: container.RestartPolicy{ - Name: "", - }, - Resources: container.Resources{ - NanoCPUs: 750000000, - Memory: 512 * 1024 * 1024, - }, - }, - } - m := &types.ContainerJSON{ - Config: &container.Config{ - Env: []string{"FOO1=BAR1", "FOO2=BAR2"}, - Labels: map[string]string{"foo1": "bar1", "foo2": "bar2"}, - }, - ContainerJSONBase: base, - } - hc := ToHostConfig(m) - res := &containers.HostConfig{ - AutoRemove: true, - RestartPolicy: containers.RestartPolicyNone, - CPULimit: 0.75, - MemoryLimit: 512 * 1024 * 1024, - } - assert.DeepEqual(t, hc, res) -} - -func TestFromRestartPolicyName(t *testing.T) { - t.Parallel() - moby := []string{"always", "on-failure", "no", ""} - ours := []string{ - containers.RestartPolicyAny, - containers.RestartPolicyOnFailure, - containers.RestartPolicyNone, - containers.RestartPolicyNone, - } - for i, p := range moby { - assert.Equal(t, fromRestartPolicyName(p), ours[i]) - } -} - -func TestToRestartPolicy(t *testing.T) { - t.Parallel() - ours := []string{containers.RestartPolicyAny, containers.RestartPolicyOnFailure, containers.RestartPolicyNone} - moby := []container.RestartPolicy{ - {Name: "always"}, - {Name: "on-failure"}, - {Name: "no"}, - } - for i, p := range ours { - assert.Equal(t, ToRestartPolicy(p), moby[i]) - } -} diff --git a/local/volumes.go b/local/volumes.go deleted file mode 100644 index 6b268e013..000000000 --- a/local/volumes.go +++ /dev/null @@ -1,79 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package local - -import ( - "context" - "fmt" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/api/types/volume" - "github.com/docker/docker/client" - - "github.com/docker/compose-cli/api/volumes" -) - -type volumeService struct { - apiClient client.APIClient -} - -func (vs *volumeService) List(ctx context.Context) ([]volumes.Volume, error) { - l, err := vs.apiClient.VolumeList(ctx, filters.NewArgs()) - if err != nil { - return []volumes.Volume{}, err - } - - res := []volumes.Volume{} - for _, v := range l.Volumes { - res = append(res, volumes.Volume{ - ID: v.Name, - Description: description(v), - }) - } - - return res, nil -} - -func (vs *volumeService) Create(ctx context.Context, name string, options interface{}) (volumes.Volume, error) { - v, err := vs.apiClient.VolumeCreate(ctx, volume.VolumeCreateBody{ - Driver: "local", - DriverOpts: nil, - Labels: nil, - Name: name, - }) - if err != nil { - return volumes.Volume{}, err - } - return volumes.Volume{ID: name, Description: description(&v)}, nil -} - -func (vs *volumeService) Delete(ctx context.Context, volumeID string, options interface{}) error { - return vs.apiClient.VolumeRemove(ctx, volumeID, false) -} - -func (vs *volumeService) Inspect(ctx context.Context, volumeID string) (volumes.Volume, error) { - v, err := vs.apiClient.VolumeInspect(ctx, volumeID) - if err != nil { - return volumes.Volume{}, err - } - return volumes.Volume{ID: volumeID, Description: description(&v)}, nil -} - -func description(v *types.Volume) string { - return fmt.Sprintf("Created %s", v.CreatedAt) -} diff --git a/logo.png b/logo.png new file mode 100644 index 000000000..9bc5eb2f9 Binary files /dev/null and b/logo.png differ diff --git a/pkg/api/labels.go b/pkg/api/labels.go index 32b0ca111..a6cf51469 100644 --- a/pkg/api/labels.go +++ b/pkg/api/labels.go @@ -21,7 +21,7 @@ import ( "github.com/hashicorp/go-version" - "github.com/docker/compose-cli/internal" + "github.com/docker/compose/v2/internal" ) const ( diff --git a/pkg/compose/attach.go b/pkg/compose/attach.go index 01b8d9e9a..dc8466d4c 100644 --- a/pkg/compose/attach.go +++ b/pkg/compose/attach.go @@ -28,8 +28,8 @@ import ( "github.com/docker/docker/pkg/stdcopy" "github.com/moby/term" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/utils" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/utils" ) func (s *composeService) attach(ctx context.Context, project *types.Project, listener api.ContainerEventListener, selectedServices []string) (Containers, error) { diff --git a/pkg/compose/build.go b/pkg/compose/build.go index 2fcb4d062..c5234a7de 100644 --- a/pkg/compose/build.go +++ b/pkg/compose/build.go @@ -33,9 +33,9 @@ import ( "github.com/moby/buildkit/session/auth/authprovider" specs "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/progress" - "github.com/docker/compose-cli/pkg/utils" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/progress" + "github.com/docker/compose/v2/pkg/utils" ) func (s *composeService) Build(ctx context.Context, project *types.Project, options api.BuildOptions) error { diff --git a/pkg/compose/build_win.go b/pkg/compose/build_win.go index 3bae16468..a38b721c5 100644 --- a/pkg/compose/build_win.go +++ b/pkg/compose/build_win.go @@ -19,7 +19,7 @@ package compose import ( "github.com/docker/buildx/build" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" ) func (s *composeService) windowsBuild(opts map[string]build.Options, mode string) error { diff --git a/pkg/compose/compose.go b/pkg/compose/compose.go index f26af89c8..daa383844 100644 --- a/pkg/compose/compose.go +++ b/pkg/compose/compose.go @@ -22,7 +22,7 @@ import ( "fmt" "strings" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" "github.com/compose-spec/compose-go/types" "github.com/docker/cli/cli/config/configfile" diff --git a/pkg/compose/containers.go b/pkg/compose/containers.go index 9ab2f61c9..5037a0116 100644 --- a/pkg/compose/containers.go +++ b/pkg/compose/containers.go @@ -23,8 +23,8 @@ import ( moby "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/utils" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/utils" ) // Containers is a set of moby Container diff --git a/pkg/compose/convergence.go b/pkg/compose/convergence.go index 89c9ba3bb..f38e5738d 100644 --- a/pkg/compose/convergence.go +++ b/pkg/compose/convergence.go @@ -33,9 +33,9 @@ import ( "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/progress" - "github.com/docker/compose-cli/pkg/utils" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/progress" + "github.com/docker/compose/v2/pkg/utils" ) const ( diff --git a/pkg/compose/cp.go b/pkg/compose/cp.go index fc1cc3058..28d923a91 100644 --- a/pkg/compose/cp.go +++ b/pkg/compose/cp.go @@ -28,7 +28,7 @@ import ( "github.com/compose-spec/compose-go/types" "github.com/docker/cli/cli/command" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" moby "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/pkg/archive" diff --git a/pkg/compose/create.go b/pkg/compose/create.go index 3f477ee4f..f8403c45f 100644 --- a/pkg/compose/create.go +++ b/pkg/compose/create.go @@ -38,9 +38,9 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/progress" - "github.com/docker/compose-cli/pkg/utils" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/progress" + "github.com/docker/compose/v2/pkg/utils" ) func (s *composeService) Create(ctx context.Context, project *types.Project, options api.CreateOptions) error { diff --git a/pkg/compose/create_test.go b/pkg/compose/create_test.go index 0303deaa4..ecca0ab43 100644 --- a/pkg/compose/create_test.go +++ b/pkg/compose/create_test.go @@ -21,7 +21,7 @@ import ( "path/filepath" "testing" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" "github.com/compose-spec/compose-go/types" composetypes "github.com/compose-spec/compose-go/types" diff --git a/pkg/compose/dependencies.go b/pkg/compose/dependencies.go index 6d65a033c..3e5b935fb 100644 --- a/pkg/compose/dependencies.go +++ b/pkg/compose/dependencies.go @@ -25,7 +25,7 @@ import ( "github.com/compose-spec/compose-go/types" "golang.org/x/sync/errgroup" - "github.com/docker/compose-cli/pkg/utils" + "github.com/docker/compose/v2/pkg/utils" ) // ServiceStatus indicates the status of a service diff --git a/pkg/compose/down.go b/pkg/compose/down.go index 273b9a66f..c41bf245b 100644 --- a/pkg/compose/down.go +++ b/pkg/compose/down.go @@ -29,8 +29,8 @@ import ( "github.com/docker/docker/errdefs" "golang.org/x/sync/errgroup" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/progress" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/progress" ) type downOp func() error diff --git a/pkg/compose/down_test.go b/pkg/compose/down_test.go index 5dcd38439..130acda58 100644 --- a/pkg/compose/down_test.go +++ b/pkg/compose/down_test.go @@ -21,8 +21,8 @@ import ( "strings" "testing" - compose "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/mocks" + compose "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/mocks" moby "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" diff --git a/pkg/compose/events.go b/pkg/compose/events.go index bc28838dd..4fdd1a308 100644 --- a/pkg/compose/events.go +++ b/pkg/compose/events.go @@ -24,9 +24,9 @@ import ( moby "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" - "github.com/docker/compose-cli/pkg/utils" + "github.com/docker/compose/v2/pkg/utils" ) func (s *composeService) Events(ctx context.Context, project string, options api.EventsOptions) error { diff --git a/pkg/compose/exec.go b/pkg/compose/exec.go index eadb8d097..74b14a6e2 100644 --- a/pkg/compose/exec.go +++ b/pkg/compose/exec.go @@ -28,7 +28,7 @@ import ( "github.com/docker/docker/pkg/stdcopy" "github.com/moby/term" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" ) func (s *composeService) Exec(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) { diff --git a/pkg/compose/filters.go b/pkg/compose/filters.go index 317353cfc..349c6d0e5 100644 --- a/pkg/compose/filters.go +++ b/pkg/compose/filters.go @@ -19,7 +19,7 @@ package compose import ( "fmt" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" "github.com/docker/docker/api/types/filters" ) diff --git a/pkg/compose/images.go b/pkg/compose/images.go index 92351570c..948d2a197 100644 --- a/pkg/compose/images.go +++ b/pkg/compose/images.go @@ -27,8 +27,8 @@ import ( "github.com/docker/docker/errdefs" "golang.org/x/sync/errgroup" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/utils" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/utils" ) func (s *composeService) Images(ctx context.Context, projectName string, options api.ImagesOptions) ([]api.ImageSummary, error) { diff --git a/pkg/compose/kill.go b/pkg/compose/kill.go index e542dcd58..308f60f1d 100644 --- a/pkg/compose/kill.go +++ b/pkg/compose/kill.go @@ -23,8 +23,8 @@ import ( moby "github.com/docker/docker/api/types" "golang.org/x/sync/errgroup" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/progress" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/progress" ) func (s *composeService) Kill(ctx context.Context, project *types.Project, options api.KillOptions) error { diff --git a/pkg/compose/kill_test.go b/pkg/compose/kill_test.go index 5a9bb9933..919caec6e 100644 --- a/pkg/compose/kill_test.go +++ b/pkg/compose/kill_test.go @@ -28,8 +28,8 @@ import ( "github.com/golang/mock/gomock" "gotest.tools/v3/assert" - compose "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/mocks" + compose "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/mocks" ) const testProject = "testProject" diff --git a/pkg/compose/logs.go b/pkg/compose/logs.go index 30786d519..d5d73ac0d 100644 --- a/pkg/compose/logs.go +++ b/pkg/compose/logs.go @@ -23,8 +23,8 @@ import ( "github.com/docker/docker/pkg/stdcopy" "golang.org/x/sync/errgroup" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/utils" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/utils" "github.com/docker/docker/api/types" ) diff --git a/pkg/compose/ls.go b/pkg/compose/ls.go index a57a10918..aa78da9ea 100644 --- a/pkg/compose/ls.go +++ b/pkg/compose/ls.go @@ -21,7 +21,7 @@ import ( "fmt" "sort" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" moby "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" diff --git a/pkg/compose/ls_test.go b/pkg/compose/ls_test.go index c964fc744..bc46071fd 100644 --- a/pkg/compose/ls_test.go +++ b/pkg/compose/ls_test.go @@ -19,7 +19,7 @@ package compose import ( "testing" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" moby "github.com/docker/docker/api/types" "gotest.tools/v3/assert" diff --git a/pkg/compose/pause.go b/pkg/compose/pause.go index cf3061138..041dc08cb 100644 --- a/pkg/compose/pause.go +++ b/pkg/compose/pause.go @@ -22,8 +22,8 @@ import ( moby "github.com/docker/docker/api/types" "golang.org/x/sync/errgroup" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/progress" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/progress" ) func (s *composeService) Pause(ctx context.Context, project string, options api.PauseOptions) error { diff --git a/pkg/compose/port.go b/pkg/compose/port.go index c9f4c069e..2b4b3b69c 100644 --- a/pkg/compose/port.go +++ b/pkg/compose/port.go @@ -20,7 +20,7 @@ import ( "context" "fmt" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" moby "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" diff --git a/pkg/compose/printer.go b/pkg/compose/printer.go index 0ba41496a..122ef7311 100644 --- a/pkg/compose/printer.go +++ b/pkg/compose/printer.go @@ -19,7 +19,7 @@ package compose import ( "fmt" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" "github.com/sirupsen/logrus" ) diff --git a/pkg/compose/ps.go b/pkg/compose/ps.go index d2fa718ce..1c27f2967 100644 --- a/pkg/compose/ps.go +++ b/pkg/compose/ps.go @@ -23,7 +23,7 @@ import ( "golang.org/x/sync/errgroup" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" ) func (s *composeService) Ps(ctx context.Context, projectName string, options api.PsOptions) ([]api.ContainerSummary, error) { diff --git a/pkg/compose/ps_test.go b/pkg/compose/ps_test.go index b66c5d03c..4b1128659 100644 --- a/pkg/compose/ps_test.go +++ b/pkg/compose/ps_test.go @@ -27,8 +27,8 @@ import ( moby "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" - compose "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/mocks" + compose "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/mocks" ) func TestPs(t *testing.T) { diff --git a/pkg/compose/pull.go b/pkg/compose/pull.go index 0202eb5f3..950ad3378 100644 --- a/pkg/compose/pull.go +++ b/pkg/compose/pull.go @@ -32,8 +32,8 @@ import ( "github.com/docker/docker/registry" "golang.org/x/sync/errgroup" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/progress" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/progress" ) func (s *composeService) Pull(ctx context.Context, project *types.Project, opts api.PullOptions) error { diff --git a/pkg/compose/push.go b/pkg/compose/push.go index 3ed58686d..3e7b15138 100644 --- a/pkg/compose/push.go +++ b/pkg/compose/push.go @@ -32,8 +32,8 @@ import ( "github.com/pkg/errors" "golang.org/x/sync/errgroup" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/progress" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/progress" ) func (s *composeService) Push(ctx context.Context, project *types.Project, options api.PushOptions) error { diff --git a/pkg/compose/remove.go b/pkg/compose/remove.go index 81d7351cf..ea30bd978 100644 --- a/pkg/compose/remove.go +++ b/pkg/compose/remove.go @@ -22,12 +22,12 @@ import ( "strings" "github.com/compose-spec/compose-go/types" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" moby "github.com/docker/docker/api/types" "golang.org/x/sync/errgroup" - "github.com/docker/compose-cli/pkg/progress" - "github.com/docker/compose-cli/pkg/prompt" + "github.com/docker/compose/v2/pkg/progress" + "github.com/docker/compose/v2/pkg/prompt" ) func (s *composeService) Remove(ctx context.Context, project *types.Project, options api.RemoveOptions) error { diff --git a/pkg/compose/restart.go b/pkg/compose/restart.go index e4f67a06f..44592c62f 100644 --- a/pkg/compose/restart.go +++ b/pkg/compose/restart.go @@ -20,11 +20,11 @@ import ( "context" "github.com/compose-spec/compose-go/types" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" "golang.org/x/sync/errgroup" - "github.com/docker/compose-cli/pkg/progress" - "github.com/docker/compose-cli/pkg/utils" + "github.com/docker/compose/v2/pkg/progress" + "github.com/docker/compose/v2/pkg/utils" ) func (s *composeService) Restart(ctx context.Context, project *types.Project, options api.RestartOptions) error { diff --git a/pkg/compose/run.go b/pkg/compose/run.go index 929c820a1..9f5aa4155 100644 --- a/pkg/compose/run.go +++ b/pkg/compose/run.go @@ -21,7 +21,7 @@ import ( "fmt" "io" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" "github.com/compose-spec/compose-go/types" "github.com/docker/cli/cli/streams" diff --git a/pkg/compose/start.go b/pkg/compose/start.go index 9359ef647..cd0aa66bb 100644 --- a/pkg/compose/start.go +++ b/pkg/compose/start.go @@ -24,8 +24,8 @@ import ( "github.com/pkg/errors" "golang.org/x/sync/errgroup" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/progress" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/progress" ) func (s *composeService) Start(ctx context.Context, project *types.Project, options api.StartOptions) error { diff --git a/pkg/compose/stop.go b/pkg/compose/stop.go index f292305ad..6655aa2b1 100644 --- a/pkg/compose/stop.go +++ b/pkg/compose/stop.go @@ -19,8 +19,8 @@ package compose import ( "context" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/progress" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/progress" "github.com/compose-spec/compose-go/types" ) diff --git a/pkg/compose/stop_test.go b/pkg/compose/stop_test.go index d61b1bdb5..f38b1b607 100644 --- a/pkg/compose/stop_test.go +++ b/pkg/compose/stop_test.go @@ -22,8 +22,8 @@ import ( "testing" "time" - compose "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/mocks" + compose "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/mocks" "github.com/compose-spec/compose-go/types" moby "github.com/docker/docker/api/types" diff --git a/pkg/compose/top.go b/pkg/compose/top.go index 4a15b98a0..dc28e3b3b 100644 --- a/pkg/compose/top.go +++ b/pkg/compose/top.go @@ -19,7 +19,7 @@ package compose import ( "context" - "github.com/docker/compose-cli/pkg/api" + "github.com/docker/compose/v2/pkg/api" "golang.org/x/sync/errgroup" ) diff --git a/pkg/compose/up.go b/pkg/compose/up.go index acf4fdf23..64b100f57 100644 --- a/pkg/compose/up.go +++ b/pkg/compose/up.go @@ -23,8 +23,8 @@ import ( "os/signal" "syscall" - "github.com/docker/compose-cli/pkg/api" - "github.com/docker/compose-cli/pkg/progress" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/progress" "github.com/compose-spec/compose-go/types" "github.com/docker/cli/cli" diff --git a/pkg/e2e/scan_message_test.go b/pkg/e2e/scan_message_test.go index 16f5dad94..8bd684bbc 100644 --- a/pkg/e2e/scan_message_test.go +++ b/pkg/e2e/scan_message_test.go @@ -23,7 +23,7 @@ import ( "strings" "testing" - "github.com/docker/compose-cli/pkg/utils" + "github.com/docker/compose/v2/pkg/utils" "gotest.tools/v3/assert" "gotest.tools/v3/icmd" diff --git a/pkg/progress/tty.go b/pkg/progress/tty.go index f04072543..293e42b0e 100644 --- a/pkg/progress/tty.go +++ b/pkg/progress/tty.go @@ -25,7 +25,7 @@ import ( "sync" "time" - "github.com/docker/compose-cli/pkg/utils" + "github.com/docker/compose/v2/pkg/utils" "github.com/buger/goterm" "github.com/morikuni/aec" diff --git a/pkg/prompt/prompt.go b/pkg/prompt/prompt.go index fd4142cd4..772de0acd 100644 --- a/pkg/prompt/prompt.go +++ b/pkg/prompt/prompt.go @@ -20,7 +20,7 @@ import ( "github.com/AlecAivazis/survey/v2" ) -//go:generate mockgen -destination=./prompt_mock.go -self_package "github.com/docker/compose-cli/pkg/prompt" -package=prompt . UI +//go:generate mockgen -destination=./prompt_mock.go -self_package "github.com/docker/compose/v2/pkg/prompt" -package=prompt . UI // UI - prompt user input type UI interface { diff --git a/scripts/install/install_linux.sh b/scripts/install/install_linux.sh deleted file mode 100644 index 0d1b3a980..000000000 --- a/scripts/install/install_linux.sh +++ /dev/null @@ -1,213 +0,0 @@ -#!/bin/sh - -# Copyright 2020 Docker Compose CLI authors - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Script to install the Docker Compose CLI on Ubuntu (Beta). - -set -eu - -RELEASE_URL=https://api.github.com/repos/docker/compose-cli/releases/latest -LINK_NAME="${LINK_NAME:-com.docker.cli}" -DRY_RUN="${DRY_RUN:-}" - -desktop_install_url="https://www.docker.com/products/docker-desktop" -engine_install_url="https://docs.docker.com/get-docker/" - -link_path="/usr/local/bin/${LINK_NAME}" -existing_cli_path="/usr/bin/docker" - -manual_install() { - echo "Please follow the manual install instructions" -} - -is_new_cli() { - cloud_version_str="$($1 version 2>/dev/null | grep 'Cloud integration' || true)" - if [ -n "$cloud_version_str" ]; then - echo 1 - else - azure_version_str="$($1 version 2>/dev/null | grep 'Azure' || true)" - if [ -n "$azure_version_str" ]; then - echo 1 - fi - echo 0 - fi -} - -echo "Running checks..." - -# Check OS -if [ "$(command -v uname)" ]; then - case "$(uname -s)" in - "Linux") - # Check for Ubuntu/Debian based distro - if ! [ -f "/etc/lsb-release" ]; then - echo "Warning: This script has been tested on Ubuntu and may not work on other distributions" - fi - # Pass - ;; - "Darwin") - echo "Error: Script not needed on macOS, please install Docker Desktop Edge: $desktop_install_url" - exit 1 - ;; - "*") - echo "Error: Unsupported OS, please follow manual instructions" - exit 1 - ;; - esac -else - # Assume Windows - echo "Error: Script not needed on Windows, please install Docker Desktop Edge: $desktop_install_url" - exit 1 -fi - -user="$(id -un 2>/dev/null || true)" -sh_c='sh -c' -sudo_sh_c='sh -c' -if [ "$user" != 'root' ]; then - if [ "$(command -v sudo)" ]; then - sudo_sh_c='sudo -E sh -c' - elif [ "$(command -v su)" ]; then - sudo_sh_c='su -c' - else - echo "Error: This installer needs the ability to run commands as root." - exit 1 - fi -fi - -if [ -n "$DRY_RUN" ]; then - sh_c='echo $sh_c' - sudo_sh_c='echo $sudo_sh_c' -fi - -# Check if Docker Engine is installed -if ! [ "$(command -v docker)" ]; then - echo "Error: Docker Engine not found" - echo "You need to install Docker first: $engine_install_url" - exit 1 -fi - -download_cmd='curl -fsSLo' -# Check that system has curl installed -if ! [ "$(command -v curl)" ]; then - echo "Error: curl not found" - echo "Please install curl" - exit 1 -fi - -if [ "$(uname -m)" = "aarch64" ]; then - DOWNLOAD_URL=${DOWNLOAD_URL:-$(curl -s ${RELEASE_URL} | grep "browser_download_url.*docker-linux-arm64" | cut -d : -f 2,3)} -elif [ "$(uname -m)" = "s390x" ]; then - DOWNLOAD_URL=${DOWNLOAD_URL:-$(curl -s ${RELEASE_URL} | grep "browser_download_url.*docker-linux-s390x" | cut -d : -f 2,3)} -else - DOWNLOAD_URL=${DOWNLOAD_URL:-$(curl -s ${RELEASE_URL} | grep "browser_download_url.*docker-linux-amd64" | cut -d : -f 2,3)} -fi - -# Check if the Compose CLI is already installed -if [ $(is_new_cli "docker") -eq 1 ]; then - if [ $(is_new_cli "/usr/local/bin/docker") -eq 1 ]; then - echo "You already have the Docker Compose CLI installed, overriding with latest version" - download_dir=$($sh_c 'mktemp -d') - $sh_c "${download_cmd} ${download_dir}/docker ${DOWNLOAD_URL}" - $sudo_sh_c "install -m 775 ${download_dir}/docker /usr/local/bin/docker" - exit 0 - fi - echo "You already have the Docker Compose CLI installed, in a different location." - exit 1 -fi - -# Check if this script has already been run -if [ -f "${link_path}" ]; then - echo "Error: This script appears to have been run as ${link_path} exists" - echo "Please uninstall and rerun this script or follow the manual instructions" - exit 1 -fi - -# Check current Docker CLI is installed to /usr/bin/ -if ! [ -f "${existing_cli_path}" ]; then - echo "Error: This script only works if the Docker CLI is installed to /usr/bin/" - manual_install - exit 1 -fi - -# Check that PATH contains /usr/bin and /usr/local/bin and that the latter is -# higher priority -path_directories=$(echo "${PATH}" | tr ":" "\n") -usr_bin_pos=-1 -usr_local_bin_pos=-1 -count=0 -for d in ${path_directories}; do - if [ "${d}" = '/usr/bin' ]; then - usr_bin_pos=$count - fi - if [ "${d}" = '/usr/local/bin' ]; then - usr_local_bin_pos=$count - fi - count=$((count + 1)) -done -if [ $usr_bin_pos -eq -1 ]; then - echo "Error: /usr/bin not found in PATH" - manual_install - exit 1 -elif [ $usr_local_bin_pos -eq -1 ]; then - echo "Error: /usr/local/bin not found in PATH" - manual_install - exit 1 -elif ! [ $usr_local_bin_pos -lt $usr_bin_pos ]; then - echo "Error: /usr/local/bin is not ordered higher than /usr/bin in your PATH" - manual_install - exit 1 -fi - -echo "Checks passed!" -echo "Downloading CLI..." - -# Download CLI to temporary directory -download_dir=$($sh_c 'mktemp -d') -$sh_c "${download_cmd} ${download_dir}/docker ${DOWNLOAD_URL}" - -echo "Downloaded CLI!" -echo "Installing CLI..." - -# Link existing Docker CLI -$sudo_sh_c "ln -s ${existing_cli_path} ${link_path}" - -# Install downloaded CLI -$sudo_sh_c "install -m 775 ${download_dir}/docker /usr/local/bin/docker" - -# Clear cache -cleared_cache=1 -if [ "$(command hash)" ]; then - $sh_c "hash -r" -elif [ "$(command rehash)" ]; then - $sh_c "rehash" -else - cleared_cache= - echo "Warning: Unable to clear command cache" -fi - -if [ -n "$DRY_RUN" ]; then - exit 0 -fi - -if [ -n "$cleared_cache" ]; then - # Check Compose CLI is working - if [ $(is_new_cli "docker") -eq 0 ]; then - echo "Error: Docker Compose CLI installation error" - exit 1 - fi - echo "Done!" -else - echo "Please log out and in again to use the Docker Compose CLI" -fi diff --git a/scripts/install/test.Dockerfile b/scripts/install/test.Dockerfile deleted file mode 100644 index c19caebee..000000000 --- a/scripts/install/test.Dockerfile +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2020 Docker Compose CLI authors - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Distro options: ubuntu, centos -ARG DISTRO=ubuntu - -FROM ubuntu:20.04 AS base-ubuntu -RUN apt-get update && apt-get install -y \ - curl -RUN curl https://get.docker.com | sh - -FROM centos:7 AS base-centos -RUN curl https://get.docker.com | sh - -FROM base-${DISTRO} AS install - -RUN apt-get update && apt-get -y install sudo -RUN adduser --disabled-password --gecos '' newuser -RUN adduser newuser sudo -RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers -USER newuser -WORKDIR /home/newuser - -COPY install_linux.sh /scripts/install_linux.sh -RUN sudo chmod +x /scripts/install_linux.sh -ARG DOWNLOAD_URL= -RUN DOWNLOAD_URL=${DOWNLOAD_URL} /scripts/install_linux.sh -RUN docker version | grep Cloud - -FROM install AS upgrade - -USER newuser -WORKDIR /home/newuser - -RUN DOWNLOAD_URL=${DOWNLOAD_URL} /scripts/install_linux.sh -RUN docker version | grep Cloud - -# To run this test locally, start an HTTP server that serves the dist/ folder -# then run a docker build passing the DOWNLOAD_URL as a build arg: -# $ cd dist/ && python3 -m http.server & -# $ docker build -f test.Dockerfile --build-arg DOWNLOAD_URL=http://192.168.0.22:8000/docker-linux-amd64.tar.gz . -# -# You can specify centos or ubuntu as distros using the DISTRO build arg. diff --git a/utils/e2e/framework.go b/utils/e2e/framework.go deleted file mode 100644 index c55eaf054..000000000 --- a/utils/e2e/framework.go +++ /dev/null @@ -1,323 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package e2e - -import ( - "fmt" - "io" - "io/ioutil" - "net/http" - "os" - "os/exec" - "path" - "path/filepath" - "runtime" - "strings" - "testing" - "time" - - "github.com/pkg/errors" - "gotest.tools/v3/assert" - is "gotest.tools/v3/assert/cmp" - "gotest.tools/v3/icmd" - "gotest.tools/v3/poll" -) - -var ( - // DockerExecutableName is the OS dependent Docker CLI binary name - DockerExecutableName = "docker" - existingExectuableName = "com.docker.cli" -) - -func init() { - if runtime.GOOS == "windows" { - DockerExecutableName = DockerExecutableName + ".exe" - existingExectuableName = existingExectuableName + ".exe" - } -} - -// E2eCLI is used to wrap the CLI for end to end testing -// nolint stutter -type E2eCLI struct { - BinDir string - ConfigDir string - test *testing.T -} - -// NewParallelE2eCLI returns a configured TestE2eCLI with t.Parallel() set -func NewParallelE2eCLI(t *testing.T, binDir string) *E2eCLI { - t.Parallel() - return newE2eCLI(t, binDir) -} - -// NewE2eCLI returns a configured TestE2eCLI -func NewE2eCLI(t *testing.T, binDir string) *E2eCLI { - return newE2eCLI(t, binDir) -} - -func newE2eCLI(t *testing.T, binDir string) *E2eCLI { - d, err := ioutil.TempDir("", "") - assert.Check(t, is.Nil(err)) - - t.Cleanup(func() { - if t.Failed() { - conf, _ := ioutil.ReadFile(filepath.Join(d, "config.json")) - t.Errorf("Config: %s\n", string(conf)) - t.Error("Contents of config dir:") - for _, p := range dirContents(d) { - t.Errorf(p) - } - } - _ = os.RemoveAll(d) - }) - - _ = os.MkdirAll(filepath.Join(d, "cli-plugins"), 0755) - composePluginFile := "docker-compose" - if runtime.GOOS == "windows" { - composePluginFile += ".exe" - } - composePlugin, err := findExecutable(composePluginFile, []string{"../../bin", "../../../bin"}) - if os.IsNotExist(err) { - fmt.Println("WARNING: docker-compose cli-plugin not found") - } - if err == nil { - err = CopyFile(composePlugin, filepath.Join(d, "cli-plugins", composePluginFile)) - if err != nil { - panic(err) - } - } - - return &E2eCLI{binDir, d, t} -} - -func dirContents(dir string) []string { - res := []string{} - _ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - res = append(res, filepath.Join(dir, path)) - return nil - }) - return res -} - -// SetupExistingCLI copies the existing CLI in a temporary directory so that the -// new CLI can be configured to use it -func SetupExistingCLI() (string, func(), error) { - p, err := exec.LookPath(existingExectuableName) - if err != nil { - p, err = exec.LookPath(DockerExecutableName) - if err != nil { - return "", nil, errors.New("existing CLI not found in PATH") - } - } - - d, err := ioutil.TempDir("", "") - if err != nil { - return "", nil, err - } - - if err := CopyFile(p, filepath.Join(d, existingExectuableName)); err != nil { - return "", nil, err - } - - bin, err := findExecutable(DockerExecutableName, []string{"../../bin", "../../../bin"}) - if err != nil { - return "", nil, err - } - - if err := CopyFile(bin, filepath.Join(d, DockerExecutableName)); err != nil { - return "", nil, err - } - - cleanup := func() { - _ = os.RemoveAll(d) - } - - return d, cleanup, nil -} - -func findExecutable(executableName string, paths []string) (string, error) { - for _, p := range paths { - bin, err := filepath.Abs(path.Join(p, executableName)) - if err != nil { - return "", err - } - - if _, err := os.Stat(bin); os.IsNotExist(err) { - continue - } - - return bin, nil - } - - return "", errors.Wrap(os.ErrNotExist, "executable not found") -} - -// CopyFile copies a file from a sourceFile to a destinationFile setting permissions to 0755 -func CopyFile(sourceFile string, destinationFile string) error { - src, err := os.Open(sourceFile) - if err != nil { - return err - } - // nolint: errcheck - defer src.Close() - - dst, err := os.OpenFile(destinationFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) - if err != nil { - return err - } - // nolint: errcheck - defer dst.Close() - - if _, err = io.Copy(dst, src); err != nil { - return err - } - - return err -} - -// NewCmd creates a cmd object configured with the test environment set -func (c *E2eCLI) NewCmd(command string, args ...string) icmd.Cmd { - env := append(os.Environ(), - "DOCKER_CONFIG="+c.ConfigDir, - "KUBECONFIG=invalid", - "TEST_METRICS_SOCKET="+c.MetricsSocket(), - "PATH="+c.PathEnvVar(), - ) - return icmd.Cmd{ - Command: append([]string{command}, args...), - Env: env, - } -} - -// MetricsSocket get the path where test metrics will be sent -func (c *E2eCLI) MetricsSocket() string { - return filepath.Join(c.ConfigDir, "./docker-cli.sock") -} - -// NewDockerCmd creates a docker cmd without running it -func (c *E2eCLI) NewDockerCmd(args ...string) icmd.Cmd { - return c.NewCmd(filepath.Join(c.BinDir, DockerExecutableName), args...) -} - -// RunDockerOrExitError runs a docker command and returns a result -func (c *E2eCLI) RunDockerOrExitError(args ...string) *icmd.Result { - fmt.Printf("\t[%s] docker %s\n", c.test.Name(), strings.Join(args, " ")) - return icmd.RunCmd(c.NewDockerCmd(args...)) -} - -// RunCmd runs a command, expects no error and returns a result -func (c *E2eCLI) RunCmd(args ...string) *icmd.Result { - fmt.Printf("\t[%s] %s\n", c.test.Name(), strings.Join(args, " ")) - assert.Assert(c.test, len(args) >= 1, "require at least one command in parameters") - res := icmd.RunCmd(c.NewCmd(args[0], args[1:]...)) - res.Assert(c.test, icmd.Success) - return res -} - -// RunDockerCmd runs a docker command, expects no error and returns a result -func (c *E2eCLI) RunDockerCmd(args ...string) *icmd.Result { - res := c.RunDockerOrExitError(args...) - res.Assert(c.test, icmd.Success) - return res -} - -// StdoutContains returns a predicate on command result expecting a string in stdout -func StdoutContains(expected string) func(*icmd.Result) bool { - return func(res *icmd.Result) bool { - return strings.Contains(res.Stdout(), expected) - } -} - -// WaitForCmdResult try to execute a cmd until resulting output matches given predicate -func (c *E2eCLI) WaitForCmdResult(command icmd.Cmd, predicate func(*icmd.Result) bool, timeout time.Duration, delay time.Duration) { - assert.Assert(c.test, timeout.Nanoseconds() > delay.Nanoseconds(), "timeout must be greater than delay") - var res *icmd.Result - checkStopped := func(logt poll.LogT) poll.Result { - fmt.Printf("\t[%s] %s\n", c.test.Name(), strings.Join(command.Command, " ")) - res = icmd.RunCmd(command) - if !predicate(res) { - return poll.Continue("Cmd output did not match requirement: %q", res.Combined()) - } - return poll.Success() - } - poll.WaitOn(c.test, checkStopped, poll.WithDelay(delay), poll.WithTimeout(timeout)) -} - -// WaitForCondition wait for predicate to execute to true -func (c *E2eCLI) WaitForCondition(predicate func() (bool, string), timeout time.Duration, delay time.Duration) { - checkStopped := func(logt poll.LogT) poll.Result { - pass, description := predicate() - if !pass { - return poll.Continue("Condition not met: %q", description) - } - return poll.Success() - } - poll.WaitOn(c.test, checkStopped, poll.WithDelay(delay), poll.WithTimeout(timeout)) -} - -// PathEnvVar returns path (os sensitive) for running test -func (c *E2eCLI) PathEnvVar() string { - path := c.BinDir + ":" + os.Getenv("PATH") - if runtime.GOOS == "windows" { - path = c.BinDir + ";" + os.Getenv("PATH") - } - return path -} - -// GoldenFile golden file specific to platform -func GoldenFile(name string) string { - if runtime.GOOS == "windows" { - return name + "-windows.golden" - } - return name + ".golden" -} - -//Lines split output into lines -func Lines(output string) []string { - return strings.Split(strings.TrimSpace(output), "\n") -} - -// HTTPGetWithRetry performs an HTTP GET on an `endpoint`, using retryDelay also as a request timeout. -// In the case of an error or the response status is not the expeted one, it retries the same request, -// returning the response body as a string (empty if we could not reach it) -func HTTPGetWithRetry(t *testing.T, endpoint string, expectedStatus int, retryDelay time.Duration, timeout time.Duration) string { - var ( - r *http.Response - err error - ) - client := &http.Client{ - Timeout: retryDelay, - } - fmt.Printf("\t[%s] GET %s\n", t.Name(), endpoint) - checkUp := func(t poll.LogT) poll.Result { - r, err = client.Get(endpoint) - if err != nil { - return poll.Continue("reaching %q: Error %s", endpoint, err.Error()) - } - if r.StatusCode == expectedStatus { - return poll.Success() - } - return poll.Continue("reaching %q: %d != %d", endpoint, r.StatusCode, expectedStatus) - } - poll.WaitOn(t, checkUp, poll.WithDelay(retryDelay), poll.WithTimeout(timeout)) - if r != nil { - b, err := ioutil.ReadAll(r.Body) - assert.NilError(t, err) - return string(b) - } - return "" -} diff --git a/utils/e2e/mockmetrics.go b/utils/e2e/mockmetrics.go deleted file mode 100644 index 681d97999..000000000 --- a/utils/e2e/mockmetrics.go +++ /dev/null @@ -1,80 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package e2e - -import ( - "io/ioutil" - "log" - "net" - "net/http" - "strings" - - "github.com/labstack/echo" -) - -// MockMetricsServer a mock registring all metrics POST invocations -type MockMetricsServer struct { - socket string - usage []string - e *echo.Echo -} - -// NewMetricsServer instaniate a new MockMetricsServer -func NewMetricsServer(socket string) *MockMetricsServer { - return &MockMetricsServer{ - socket: socket, - e: echo.New(), - } -} - -func (s *MockMetricsServer) handlePostUsage(c echo.Context) error { - body, error := ioutil.ReadAll(c.Request().Body) - if error != nil { - return error - } - cliUsage := string(body) - s.usage = append(s.usage, cliUsage) - return c.String(http.StatusOK, "") -} - -// GetUsage get usage -func (s *MockMetricsServer) GetUsage() []string { - return s.usage -} - -// ResetUsage reset usage -func (s *MockMetricsServer) ResetUsage() { - s.usage = []string{} -} - -// Stop stop the mock server -func (s *MockMetricsServer) Stop() { - _ = s.e.Close() -} - -// Start start the mock server -func (s *MockMetricsServer) Start() { - go func() { - listener, err := net.Listen("unix", strings.TrimPrefix(s.socket, "unix://")) - if err != nil { - log.Fatal(err) - } - s.e.Listener = listener - s.e.POST("/usage", s.handlePostUsage) - _ = s.e.Start(":1323") - }() -} diff --git a/utils/formatter/container.go b/utils/formatter/container.go deleted file mode 100644 index 5dcdf3421..000000000 --- a/utils/formatter/container.go +++ /dev/null @@ -1,129 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package formatter - -import ( - "fmt" - "sort" - "strconv" - "strings" - - "github.com/docker/compose-cli/api/containers" -) - -type portGroup struct { - first uint32 - last uint32 -} - -// PortsToStrings returns a human readable published ports -func PortsToStrings(ports []containers.Port, fqdn string) []string { - groupMap := make(map[string]*portGroup) - result := []string{} - var ( - hostMappings []string - groupMapKeys []string - ) - - sort.Slice(ports, func(i int, j int) bool { - return comparePorts(ports[i], ports[j]) - }) - - for _, port := range ports { - // Simple case: HOST_IP:PORT1:PORT2 - hostIP := "0.0.0.0" - if port.HostIP != "" { - hostIP = port.HostIP - } - if fqdn != "" { - hostIP = fqdn - } - - if port.HostPort != port.ContainerPort { - hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", hostIP, port.HostPort, port.ContainerPort, port.Protocol)) - continue - } - - current := port.ContainerPort - portKey := fmt.Sprintf("%s/%s", hostIP, port.Protocol) - group := groupMap[portKey] - - if group == nil { - groupMap[portKey] = &portGroup{first: current, last: current} - // record order that groupMap keys are created - groupMapKeys = append(groupMapKeys, portKey) - continue - } - - if current == (group.last + 1) { - group.last = current - continue - } - - result = append(result, formGroup(portKey, group.first, group.last)) - groupMap[portKey] = &portGroup{first: current, last: current} - } - - for _, portKey := range groupMapKeys { - g := groupMap[portKey] - result = append(result, formGroup(portKey, g.first, g.last)) - } - - result = append(result, hostMappings...) - - return result -} - -func formGroup(key string, start uint32, last uint32) string { - parts := strings.Split(key, "/") - protocol := parts[0] - var ip string - if len(parts) > 1 { - ip = parts[0] - protocol = parts[1] - } - group := strconv.Itoa(int(start)) - - // add range - if start != last { - group = fmt.Sprintf("%s-%d", group, last) - } - - // add host ip - if ip != "" { - group = fmt.Sprintf("%s:%s->%s", ip, group, group) - } - - // add protocol - return fmt.Sprintf("%s/%s", group, protocol) -} - -func comparePorts(i containers.Port, j containers.Port) bool { - if i.ContainerPort != j.ContainerPort { - return i.ContainerPort < j.ContainerPort - } - - if i.HostIP != j.HostIP { - return i.HostIP < j.HostIP - } - - if i.HostPort != j.HostPort { - return i.HostPort < j.HostPort - } - - return i.Protocol < j.Protocol -} diff --git a/utils/formatter/container_test.go b/utils/formatter/container_test.go deleted file mode 100644 index d590d24ff..000000000 --- a/utils/formatter/container_test.go +++ /dev/null @@ -1,88 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package formatter - -import ( - "testing" - - "gotest.tools/v3/assert" - - "github.com/docker/compose-cli/cli/options/run" -) - -func TestDisplayPortsNoDomainname(t *testing.T) { - testCases := []struct { - name string - in []string - expected []string - }{ - { - name: "simple", - in: []string{"80"}, - expected: []string{"0.0.0.0:80->80/tcp"}, - }, - { - name: "different ports", - in: []string{"80:90"}, - expected: []string{"0.0.0.0:80->90/tcp"}, - }, - { - name: "host ip", - in: []string{"192.168.0.1:80:90"}, - expected: []string{"192.168.0.1:80->90/tcp"}, - }, - { - name: "port range", - in: []string{"80-90:80-90"}, - expected: []string{"0.0.0.0:80-90->80-90/tcp"}, - }, - { - name: "grouping", - in: []string{"80:80", "81:81"}, - expected: []string{"0.0.0.0:80-81->80-81/tcp"}, - }, - { - name: "groups", - in: []string{"80:80", "82:82"}, - expected: []string{"0.0.0.0:80->80/tcp", "0.0.0.0:82->82/tcp"}, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - runOpts := run.Opts{ - Publish: testCase.in, - } - containerConfig, err := runOpts.ToContainerConfig("test") - assert.NilError(t, err) - - out := PortsToStrings(containerConfig.Ports, "") - assert.DeepEqual(t, testCase.expected, out) - }) - } -} - -func TestDisplayPortsWithDomainname(t *testing.T) { - runOpts := run.Opts{ - Publish: []string{"80"}, - } - containerConfig, err := runOpts.ToContainerConfig("test") - assert.NilError(t, err) - - out := PortsToStrings(containerConfig.Ports, "mydomain.westus.azurecontainner.io") - assert.DeepEqual(t, []string{"mydomain.westus.azurecontainner.io:80->80/tcp"}, out) -} diff --git a/utils/logs.go b/utils/logs.go deleted file mode 100644 index be0526401..000000000 --- a/utils/logs.go +++ /dev/null @@ -1,59 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package utils - -import ( - "github.com/docker/compose-cli/pkg/api" -) - -// FilteredLogConsumer filters logs for given services -func FilteredLogConsumer(consumer api.LogConsumer, services []string) api.LogConsumer { - if len(services) == 0 { - return consumer - } - allowed := map[string]bool{} - for _, s := range services { - allowed[s] = true - } - return &allowListLogConsumer{ - allowList: allowed, - delegate: consumer, - } -} - -type allowListLogConsumer struct { - allowList map[string]bool - delegate api.LogConsumer -} - -func (a *allowListLogConsumer) Log(container, service, message string) { - if a.allowList[service] { - a.delegate.Log(container, service, message) - } -} - -func (a *allowListLogConsumer) Status(container, message string) { - if a.allowList[container] { - a.delegate.Status(container, message) - } -} - -func (a *allowListLogConsumer) Register(name string) { - if a.allowList[name] { - a.delegate.Register(name) - } -} diff --git a/utils/units.go b/utils/units.go deleted file mode 100644 index 401dddbe7..000000000 --- a/utils/units.go +++ /dev/null @@ -1,52 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package utils - -import ( - "github.com/docker/go-units" -) - -// MemBytes is a type for human readable memory bytes (like 128M, 2g, etc) -type MemBytes int64 - -// String returns the string format of the human readable memory bytes -func (m *MemBytes) String() string { - // NOTE: In spf13/pflag/flag.go, "0" is considered as "zero value" while "0 B" is not. - // We return "0" in case value is 0 here so that the default value is hidden. - // (Sometimes "default 0 B" is actually misleading) - if m.Value() != 0 { - return units.BytesSize(float64(m.Value())) - } - return "0" -} - -// Set sets the value of the MemBytes by passing a string -func (m *MemBytes) Set(value string) error { - val, err := units.RAMInBytes(value) - *m = MemBytes(val) - return err -} - -// Type returns the type -func (m *MemBytes) Type() string { - return "bytes" -} - -// Value returns the value in int64 -func (m *MemBytes) Value() int64 { - return int64(*m) -} diff --git a/utils/units_test.go b/utils/units_test.go deleted file mode 100644 index 2e35e11b2..000000000 --- a/utils/units_test.go +++ /dev/null @@ -1,60 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package utils - -import ( - "testing" - - "gotest.tools/v3/assert" - "gotest.tools/v3/assert/cmp" -) - -const ( - b = 1 - kb = 1024 * b - mb = 1024 * kb - gb = 1024 * mb -) - -func TestMemBytes(t *testing.T) { - var m MemBytes - assert.Assert(t, cmp.Nil(m.Set("42"))) - assert.Equal(t, int64(42), m.Value()) - assert.Equal(t, "42B", m.String()) - - assert.Assert(t, cmp.Nil(m.Set("1b"))) - assert.Equal(t, int64(1), m.Value()) - assert.Equal(t, "1B", m.String()) - - assert.Assert(t, cmp.Nil(m.Set("1k"))) - assert.Equal(t, int64(kb), m.Value()) - assert.Equal(t, "1KiB", m.String()) - - assert.Assert(t, cmp.Nil(m.Set("1m"))) - assert.Equal(t, int64(mb), m.Value()) - assert.Equal(t, "1MiB", m.String()) - - assert.Assert(t, cmp.Nil(m.Set("1g"))) - assert.Equal(t, int64(gb), m.Value()) - assert.Equal(t, "1GiB", m.String()) - - assert.Assert(t, cmp.Nil(m.Set("42g"))) - assert.Equal(t, int64(42*gb), m.Value()) - assert.Equal(t, "42GiB", m.String()) - - assert.Error(t, m.Set("###"), "invalid size: '###'") -}