---
version: '3'

vars:
  DIND_MOUNT_MAPPING: /var/run/docker.sock:/var/run/docker.sock
  DOCKER_BASE_TAG:
    sh: |
      if [ -f Dockerfile ] && type jq &> /dev/null; then
        BASE_TAG="$(jq -r '.blueprint.dockerBaseTag' package.json)"
        if [ "$BASE_TAG" != 'null' ]; then
          echo "$BASE_TAG"
        else
          if grep -m1 "^FROM .* AS .*" Dockerfile > /dev/null; then
            FIRST_LINE=$(head -n 1 Dockerfile | sed 's/.* AS \(.*\)/\1/')
            if [ -n "$FIRST_LINE" ]; then
              echo "$FIRST_LINE"
            else
              echo ""
            fi
          else
            echo ""
          fi
        fi
      fi
  DOCKER_IMAGE:
    sh: |
      if type jq &> /dev/null; then
        echo "{{.DOCKERHUB_PROFILE}}/$(jq -r '.blueprint.slug' package.json)"
      fi

tasks:
  build:
    desc: Build a regular Docker image and then generate a slim build from it
    summary: |
      # Build the Docker Images

      This task will build all of the corresponding Docker images as long as they
      have a [container-structure-test](https://github.com/GoogleContainerTools/container-structure-test)
      created.

      For regular projects, having a `Docker.test.yml` container-structure-test is enough. However,
      for projects with multiple build targets, there should be a test for each target. So, if there
      are 2 build targets in the `Dockerfile` named `build1` and `build2` then there should be
      a `Docker.build1.test.yml` and `Docker.build2.test.yml` container-structure-test.
    hide:
      sh: '[ ! -f Dockerfile ]'
    log:
      error: Encountered error while running Docker build sequence
      start: Running Docker build sequence
      success: Finished running Docker build sequence
    cmds:
      - task: ensure:running
      - task: taskfile:deps
      - |
        if [ -d ./test/structure ]; then
          TESTS="$(find ./test/structure -mindepth 1 -maxdepth 1 -type f -name "*.yml")"
          if echo "$TESTS" | grep '.yml' > /dev/null; then
            for TEST_FILE in $TESTS; do
              TARGET="$(echo "$TEST_FILE" | sed 's/.*\/\([^\/]*\).yml$/\1/')"
              if [ "$(jq --arg slug "$TARGET" '.blueprint.dockerSlimCommand | type' package.json)" == 'object' ]; then
                if [ "$(jq --arg slug "$TARGET" '.blueprint.dockerSlimCommand[$slug]' package.json)" != 'null' ]; then
                  task docker:build:slim -- "$TARGET"
                else
                  .config/log warn 'The `.blueprint.dockerSlimCommand` is missing - skipping slim build and building just a regular image'
                  task docker:build:fat -- "$TARGET"
                fi
              else
                if [ "$(jq --arg slug "$TARGET" '.blueprint.dockerSlimCommand' package.json)" != 'null' ]; then
                  task docker:build:slim -- "$TARGET"
                else
                  task docker:build:fat -- "$TARGET"
                fi
              fi
            done
          else
            .config/log error 'The `./test/structure` folder is present but there are no container-structure-tests in it. Add tests \
            for each build target in the `Dockerfile` that you would like to generate an image for.'
            exit 1
          fi
        else
          .config/log info 'No `./test/structure` folder is present so assuming there is only one build target'
          if [ "$(jq -r '.blueprint.dockerSlimCommand' package.json)" != null ]; then
            .config/log info '`.blueprint.dockerSlimCommand` is present in `package.json` so building both a `latest` and `slim` image'
            task docker:build:slim
          else
            task docker:build:fat
          fi
        fi

  ensure:running:
    summary: |
      Ensures Docker Desktop is running if on a macOS machine.
    cmds:
      - |
        if ! docker stats --no-stream; then
          if [ -f /Applications/Docker.app ]; then
            open /Applications/Docker.app
            while (! docker stats --no-stream); do
              echo "Waiting for Docker to launch..."
              sleep 1
            done
          else
            .config/log error 'Docker is either not running or not installed!'
          fi
        fi
    status:
      - '[[ "{{OS}}" != "darwin" ]]'

  login:
    deps:
      - :install:software:docker
    log:
      error: Failed to authenticate `{{.DOCKERHUB_USER}}` with the DockerHub registry
      start: Logging into DockerHub registry with `{{.DOCKERHUB_USER}}`
      success: Authenticated to DockerHub registry with `{{.DOCKERHUB_USER}}`
    cmds:
      - |
        if [ -n "$DOCKERHUB_REGISTRY_PASSWORD" ]; then
          echo "$DOCKERHUB_REGISTRY_PASSWORD" | docker login -u {{.DOCKERHUB_USER}} --password-stdin
        else
          .config/log warn 'The `DOCKERHUB_REGISTRY_PASSWORD` is not an environment variable. This is required to login to DockerHub.'
        fi

  prepare:
    cmds:
      - task: build
    status:
      - '[ ! -f Dockerfile ]'

  publish:
    desc: Publish the Docker images (using `Docker*.test.yml` files)
    hide:
      sh: '[ ! -f Dockerfile ]'
    summary: |
      # Publish the Docker Images

      This task will publish all of the corresponding Docker images as long as they
      have a [container-structure-test](https://github.com/GoogleContainerTools/container-structure-test)
      created.

      For regular projects, having a `Docker.test.yml` container-structure-test is enough. However,
      for projects with multiple build targets, there should be a test for each target. So, if there
      are 2 build targets in the `Dockerfile` named `build1` and `build2` then there should be
      a `Docker.build1.test.yml` and `Docker.build2.test.yml` container-structure-test.
    cmds:
      - |
        if [ -d ./test/structure ]; then
          TESTS="$(find ./test/structure -mindepth 1 -maxdepth 1 -type f -name "*.yml")"
          if echo "$TESTS" | grep '.yml'; then
            for TEST_FILE in $TESTS; do
              TARGET="$(echo "$TEST_FILE" | sed 's/.*\/\([^\/]*\).yml$/\1/')"
              task docker:publish:images -- "$TARGET"
            done
          else
            .config/log error 'The `./test/structure` folder is present but there are no container-structure-tests in it. Add tests \
            for each build target in the `Dockerfile` that you would like to generate an image for.'
            exit 1
          fi
        else
          .config/log info 'No `./test/structure` folder is present so assuming there is only one build target'
          task docker:publish:images
        fi
      - task: pushrm
    status:
      - '[ ! -f Dockerfile ]'

  publish:image:
    vars:
      SLIM_ENABLED:
        sh: |
          TYPE="$(jq -r '.blueprint.dockerSlimCommand | type' package.json)"
          if [ "$TYPE" != 'object' ]; then
            jq -r '.blueprint.dockerSlimCommand' package.json | sed 's/null/false/'
          else
            TARGET="{{if .CLI_ARGS}}{{.CLI_ARGS}}{{else}}{{.TARGET}}{{end}}"
            jq --arg target "$TARGET" -r '.blueprint.dockerSlimCommand[$target]' package.json | sed 's/null/false/'
          fi
    log:
      error: Failed to tag / push `{{.DOCKER_IMAGE}}:{{.TARGET_TAG}}`
      start: Tagging and pushing `{{.DOCKER_IMAGE}}:{{.TARGET_TAG}}`
      success: Finished uploading `{{.DOCKER_IMAGE}}:{{.TARGET_TAG}}`
    cmds:
      - docker tag {{.DOCKER_IMAGE}}:{{.SOURCE_TAG}} {{.DOCKER_IMAGE}}:{{.TARGET_TAG}}
      - docker push {{.DOCKER_IMAGE}}:{{.TARGET_TAG}}
    status:
      - '[[ "{{.SOURCE_TAG}}" == "slim"* ]]'
      - '[[ "{{.SLIM_ENABLED}}" == "false" ]]'

  publish:images:
    vars:
      MAJOR_VERSION:
        sh: jq -r '.version' package.json | sed 's/\..*\..*$//'
      MINOR_VERSION:
        sh: jq -r '.version' package.json | sed 's/^[^\.]*\.//' | sed 's/\.[^\.]*$//'
      TAG_POST: '{{if ne .DOCKER_BASE_TAG .CLI_ARGS}}-{{if (contains "codeclimate" .CLI_ARGS)}}codeclimate{{else}}{{.CLI_ARGS}}{{end}}{{end}}'
      TARGET: '{{if .CLI_ARGS}}{{.CLI_ARGS}}{{else}}{{.TARGET}}{{end}}'
      VERSION:
        sh: jq -r ".version" package.json
    log:
      error: An error occurred while publishing the Docker images
      start: Publishing Docker images
      success: Finished uploading all Docker images
    cmds:
      - task: publish:image
        vars:
          SOURCE_TAG: latest{{.TAG_POST}}
          TARGET_TAG: 'v{{.VERSION}}{{.TAG_POST}}'
      - task: publish:image
        vars:
          SOURCE_TAG: latest{{.TAG_POST}}
          TARGET_TAG: 'v{{.MAJOR_VERSION}}{{.TAG_POST}}'
      - task: publish:image
        vars:
          SOURCE_TAG: latest{{.TAG_POST}}
          TARGET_TAG: v{{.MAJOR_VERSION}}.{{.MINOR_VERSION}}{{.TAG_POST}}
      - task: publish:image
        vars:
          SOURCE_TAG: latest{{.TAG_POST}}
          TARGET_TAG: latest{{.TAG_POST}}
      - task: publish:image
        vars:
          SOURCE_TAG: slim{{.TAG_POST}}
          TARGET_TAG: 'v{{.VERSION}}-slim{{.TAG_POST}}'
      - task: publish:image
        vars:
          SOURCE_TAG: slim{{.TAG_POST}}
          TARGET_TAG: 'v{{.MAJOR_VERSION}}-slim{{.TAG_POST}}'
      - task: publish:image
        vars:
          SOURCE_TAG: slim{{.TAG_POST}}
          TARGET_TAG: v{{.MAJOR_VERSION}}.{{.MINOR_VERSION}}-slim{{.TAG_POST}}
      - task: publish:image
        vars:
          SOURCE_TAG: slim{{.TAG_POST}}
          TARGET_TAG: slim{{.TAG_POST}}

  pushrm:
    deps:
      - :install:github:docker-pushrm
      - :install:software:docker
    vars:
      DOCKERHUB_DESCRIPTION:
        sh: jq -r '.description' package.json
    env:
      DOCKER_PASS:
        sh: echo "$DOCKERHUB_REGISTRY_PASSWORD"
      DOCKER_USER: '{{.DOCKERHUB_USER}}'
    cmds:
      - cmd: docker pushrm {{.DOCKER_IMAGE}}
        ignore_error: true
      - cmd: docker pushrm --short '{{.DOCKERHUB_DESCRIPTION}}' {{.DOCKER_IMAGE}}
        ignore_error: true

  shell:
    deps:
      - :install:software:docker
      - :install:software:jq
    interactive: true
    desc: Open the terminal of an existing Docker image
    hide:
      sh: '[ ! -f Dockerfile ]'
    summary: |
      # Shell into Docker Image

      This task will start a shell session with one of the Docker images
      that are currently present on machine. The task looks at the output
      from `docker images` and filters the list based on the project's
      expected DockerHub slug or a string, if you pass in CLI arguments.

      **Displays a list of images that match the name in package.json:**
      `task docker:shell`

      **Displays a list of images that have the pattern abc in their name:**
      `task docker:shell -- 'abc'`
    vars:
      IMAGE_OPTIONS:
        sh: docker images | grep {{.DOCKER_IMAGE}} | sed 's/^\([^ ]*\).*$/\1/' | jq -Rsc 'split("\n")' | jq 'del(.[-1])'
    prompt:
      type: select
      message: Which Docker image would you like to shell into?
      options: '{{.IMAGE_OPTIONS}}'
      answer:
        cmds:
          - docker run -v "${PWD}:/work" -w /work -it --entrypoint /bin/sh --rm {{.ANSWER}}

  taskfile:deps:
    vars:
      DOCKER_TASKS_TAG:
        sh: jq -r '.blueprint.dockerTasksTag' package.json
    cmds:
      - task: :common:util:task:tag:deps
        vars:
          TAG: '{{.DOCKER_TASKS_TAG}}'
    status:
      - '[ "{{.DOCKER_TASKS_TAG}}" == "null" ]'

  test:
    deps:
      - :install:software:docker
    desc: Perform all available tests on the Docker image
    hide:
      sh: '[ ! -f Dockerfile ]'
    summary: |
      # Test the Docker Images

      This task will publish all of the corresponding Docker images as long as they
      have a [container-structure-test](https://github.com/GoogleContainerTools/container-structure-test)
      created.

      For regular projects, having a `Docker.test.yml` container-structure-test is enough. However,
      for projects with multiple build targets, there should be a test for each target. So, if there
      are 2 build targets in the `Dockerfile` named `build1` and `build2` then there should be
      a `Docker.build1.test.yml` and `Docker.build2.test.yml` container-structure-test.

      ## Comparing Slim Output to Latest Output

      If the `dockerSlimCommand` is present in the `blueprint` section of `package.json`, the output from running
      `npm run test:dockerslim` on every project/folder in the `test-output/` folder will be compared. The comparison
      will ensure that the `slim` output matches the `latest` output. Each folder in the `test-output/` folder must
      have a `package.json` file present with the `test:dockerslim` script defined under `scripts`.

      If there is no `test-output/` folder, then this kind of test is skipped.

      ## `dockerSlimCommand` with Multiple Build Targets

      If there are multiple build targets, then the `dockerSlimCommand` should be an object with keys equal to
      the build targets and values equal to the appropriate DockerSlim command. If you are only using
      `Docker.test.yml` then you can simply set `dockerSlimCommand` equal to the appropriate command. However,
      if you have two targets named `build1` and `build2`, then your `dockerSlimCommand` should look something
      like:

      ```json
      {
        "blueprint": {
          "dockerSlimCommand": {
            "build1": "...DockerSlim build command options here for build1",
            "build2": "...DockerSlim build command options here for build2"
          }
        }
      }
      ```

      ## CodeClimate CLI Testing

      Any folder in the `test/` folder that starts with `codeclimate` gets scanned by CodeClimate CLI.
    log:
      error: Encountered error during Docker test sequence
      start: Initiating Docker test sequence
      success: Successfully completed Docker test sequence
    cmds:
      - task: :docker:test:container-structure-tests
      - task: :docker:test:output
      - task: :docker:test:codeclimate
      - task: :docker:test:gitlab-runner

  verify:
    cmds:
      - task: login

  version:software:
    cmds:
      - |
        if grep -q "CMD.\[\"--version\"\]" Dockerfile; then
          VERSION=$(docker run --cap-drop=ALL -e PY_COLORS=0 --rm {{.DOCKER_IMAGE}}:latest | perl \
            -pe 'if(($v)=/([0-9]+([.][0-9]+)+)/){print"$v";exit}$_=""')
          if [[ $VERSION == *.*.* ]]; then
            echo $VERSION
          elif [[ $VERSION == *.* ]]; then
            echo $VERSION.0
          fi
        fi
